Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tasks assigned to me view (#9567) #9568

Merged
merged 13 commits into from
Feb 17, 2025
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ export const recordIndexRecordGroupHideComponentFamilyState =
case ViewType.Kanban:
return false;
case ViewType.Table:
return true;
return false;
default:
return false;
}
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { ConfirmationQuestion } from 'src/database/commands/questions/confirmati
import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module';
import { UpgradeTo0_41CommandModule } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module';
import { UpgradeTo0_42CommandModule } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module';
import { UpgradeTo0_43CommandModule } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
@@ -53,6 +54,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
UpgradeTo0_40CommandModule,
UpgradeTo0_41CommandModule,
UpgradeTo0_42CommandModule,
UpgradeTo0_43CommandModule,
FeatureFlagModule,
],
providers: [
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { InjectRepository } from '@nestjs/typeorm';

import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';

import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { isCommandLogger } from 'src/database/commands/logger';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity';
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';

@Command({
name: 'upgrade-0.43:add-tasks-assigned-to-me-view',
description: 'Add tasks assigned to me view',
})
export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository);
}

async executeActiveWorkspacesCommand(
_passedParam: string[],
options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to create many to one relations');

if (isCommandLogger(this.logger)) {
this.logger.setVerbose(options.verbose ?? false);
}

try {
for (const [index, workspaceId] of workspaceIds.entries()) {
await this.processWorkspace(workspaceId, index, workspaceIds.length);
}

this.logger.log(chalk.green('Command completed!'));
} catch (error) {
this.logger.log(chalk.red('Error in workspace'));
}
}

private async processWorkspace(
workspaceId: string,
index: number,
total: number,
): Promise<void> {
try {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);

const viewId = await this.createTasksAssignedToMeView(workspaceId);

await this.createTasksAssignedToMeViewGroups(workspaceId, viewId);

await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);

this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
} catch {
this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`));
}
}

private async createTasksAssignedToMeView(
workspaceId: string,
): Promise<string> {
const objectMetadata = await this.objectMetadataRepository.find({
where: { workspaceId },
relations: ['fields'],
});

const objectMetadataMap = objectMetadata.reduce((acc, object) => {
acc[object.standardId ?? ''] = {
id: object.id,
fields: object.fields.reduce((acc, field) => {
acc[field.standardId ?? ''] = field.id;

return acc;
}, {}),
};

return acc;
}, {});

const taskObjectMetadata = objectMetadata.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.task,
);

if (!taskObjectMetadata) {
throw new Error(`Task object not found for workspace ${workspaceId}`);
}

const viewRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
workspaceId,
'view',
false,
);

const existingView = await viewRepository.findOne({
where: {
name: 'Assigned to Me',
objectMetadataId: taskObjectMetadata.id,
},
});

if (existingView) {
throw new Error(
`"Assigned to Me" view already exists for workspace ${workspaceId}`,
);
}

const viewDefinition = tasksAssignedToMeView(objectMetadataMap);
const viewId = v4();

const insertedView = await viewRepository.save({
id: viewId,
name: viewDefinition.name,
objectMetadataId: viewDefinition.objectMetadataId,
type: viewDefinition.type,
position: viewDefinition.position,
icon: viewDefinition.icon,
kanbanFieldMetadataId: viewDefinition.kanbanFieldMetadataId,
});

if (viewDefinition.fields && viewDefinition.fields.length > 0) {
const viewFieldRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>(
workspaceId,
'viewField',
false,
);

const viewFields = viewDefinition.fields.map((field) => ({
fieldMetadataId: field.fieldMetadataId,
position: field.position,
isVisible: field.isVisible,
size: field.size,
viewId: insertedView.id,
}));

await viewFieldRepository.save(viewFields);
}

if (viewDefinition.filters && viewDefinition.filters.length > 0) {
const viewFilterRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFilterWorkspaceEntity>(
workspaceId,
'viewFilter',
false,
);

const viewFilters = viewDefinition.filters.map((filter) => ({
fieldMetadataId: filter.fieldMetadataId,
displayValue: filter.displayValue,
operand: filter.operand,
value: filter.value,
viewId: insertedView.id,
}));

await viewFilterRepository.save(viewFilters);
}

return insertedView.id;
}

private async createTasksAssignedToMeViewGroups(
workspaceId: string,
viewId: string,
) {
const taskStatusFieldMetadata = await this.fieldMetadataRepository.findOne({
where: {
workspaceId,
standardId: TASK_STANDARD_FIELD_IDS.status,
},
});

if (!taskStatusFieldMetadata) {
throw new Error(
`Task status field metadata not found for workspace ${workspaceId}`,
);
}

const optionValueViewGroups = taskStatusFieldMetadata.options.map(
(taskStatusOption: FieldMetadataDefaultOption, index) =>
({
fieldMetadataId: taskStatusFieldMetadata.id,
viewId,
fieldValue: taskStatusOption.value,
position: index,
}) satisfies Partial<ViewGroupWorkspaceEntity>,
);

const noValueViewGroup: Partial<ViewGroupWorkspaceEntity> = {
fieldMetadataId: taskStatusFieldMetadata.id,
viewId,
fieldValue: '',
position: optionValueViewGroups.length,
};

const viewGroups = [...optionValueViewGroups, noValueViewGroup];

const viewGroupRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
workspaceId,
'viewGroup',
false,
);

await viewGroupRepository.insert(viewGroups);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { InjectRepository } from '@nestjs/typeorm';

import { Command } from 'nest-commander';
import { Repository } from 'typeorm';

import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';

@Command({
name: 'upgrade-0.43',
description: 'Upgrade to 0.43',
})
export class UpgradeTo0_43Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
private readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
) {
super(workspaceRepository);
}

async executeActiveWorkspacesCommand(
passedParam: string[],
options: BaseCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to upgrade to 0.43');

await this.addTasksAssignedToMeViewCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command';
import { UpgradeTo0_43Command } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';

@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity],
'metadata',
),
WorkspaceMigrationRunnerModule,
WorkspaceMigrationModule,
WorkspaceMetadataVersionModule,
],
providers: [UpgradeTo0_43Command, AddTasksAssignedToMeViewCommand],
})
export class UpgradeTo0_43CommandModule {}
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-obje
import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view';
import { peopleAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view';
import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view';
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view';
import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view';
import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view';
@@ -26,6 +27,7 @@ export const seedViewWithDemoData = async (
opportunitiesByStageView(objectMetadataStandardIdToIdMap),
notesAllView(objectMetadataStandardIdToIdMap),
tasksAllView(objectMetadataStandardIdToIdMap),
tasksAssignedToMeView(objectMetadataStandardIdToIdMap),
tasksByStatusView(objectMetadataStandardIdToIdMap),
workflowsAllView(objectMetadataStandardIdToIdMap),
workflowVersionsAllView(objectMetadataStandardIdToIdMap),
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';

import {
BASE_OBJECT_STANDARD_FIELD_IDS,
TASK_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';

export const tasksAssignedToMeView = (
objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap,
) => {
return {
name: 'Assigned to Me',
objectMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].id,
type: 'table',
key: null,
position: 2,
icon: 'IconUserCircle',
kanbanFieldMetadataId: '',
filters: [
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.assignee
],
displayValue: 'Me',
operand: 'is',
value: JSON.stringify({
isCurrentWorkspaceMemberSelected: true,
selectedRecordIds: [],
}),
},
],
fields: [
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.title
],
position: 0,
isVisible: true,
size: 210,
},
/*{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.status
],
position: 2,
isVisible: true,
size: 150,
},*/
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.taskTargets
],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.createdBy
],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.dueAt
],
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.assignee
],
position: 6,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.body
],
position: 7,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
],
position: 8,
isVisible: true,
size: 150,
},
],
groups: [
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.status
],
isVisible: true,
fieldValue: 'TODO',
position: 0,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.status
],
isVisible: true,
fieldValue: 'IN_PROGRESS',
position: 1,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.status
],
isVisible: true,
fieldValue: 'DONE',
position: 2,
},
{
fieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[
TASK_STANDARD_FIELD_IDS.status
],
isVisible: true,
fieldValue: '',
position: 3,
},
],
};
};
Original file line number Diff line number Diff line change
@@ -10,12 +10,12 @@ export const tasksByStatusView = (
objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap,
) => {
return {
name: 'By status',
name: 'By Status',
objectMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].id,
type: 'kanban',
key: null,
position: 0,
position: 1,
icon: 'IconLayoutKanban',
kanbanFieldMetadataId:
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.task].fields[