feat: add new ACTOR field type and createdBy standard fields (#6324)
This pull request introduces a new `FieldMetadataType` called `ACTOR`.
The primary objective of this new type is to add an extra column to the
following objects: `person`, `company`, `opportunity`, `note`, `task`,
and all custom objects.
This composite type contains three properties:
- `source`
```typescript
export enum FieldActorSource {
EMAIL = 'EMAIL',
CALENDAR = 'CALENDAR',
API = 'API',
IMPORT = 'IMPORT',
MANUAL = 'MANUAL',
}
```
- `workspaceMemberId`
- This property can be `undefined` in some cases and refers to the
member who created the record.
- `name`
- Serves as a fallback if the `workspaceMember` is deleted and is used
for other source types like `API`.
### Functionality
The pre-hook system has been updated to allow real-time argument
updates. When a record is created, a pre-hook can now compute and update
the arguments accordingly. This enhancement enables the `createdBy`
field to be populated with the correct values based on the
`authContext`.
The `authContext` now includes:
- An optional User entity
- An optional ApiKey entity
- The workspace entity
This provides access to the necessary data for the `createdBy` field.
In the GraphQL API, only the `source` can be specified in the
`createdBy` input. This allows the front-end to specify the source when
creating records from a CSV file.
### Front-End Handling
On the front-end, `orderBy` and `filter` are only applied to the name
property of the `ACTOR` composite type. Currently, we are unable to
apply these operations to the workspace member relation. This means that
if a workspace member changes their first name or last name, there may
be a mismatch because the name will differ from the new one. The name
displayed on the screen is based on the workspace member entity when
available.
### Missing Components
Currently, this PR does not include a `createdBy` value for the `MAIL`
and `CALENDAR` sources. These records are created in a job, and at
present, we only have access to the workspaceId within the job. To
address this, we should use a function similar to
`loadServiceWithContext`, which was recently removed from `TwentyORM`.
This function would allow us to pass the `authContext` to the jobs
without disrupting existing jobs.
Another PR will be created to handle these cases.
### Related Issues
Fixes issue #5155.
### Additional Notes
This PR doesn't include the migrations of the current records and views.
Everything works properly when the database is reset but this part is
still missing for now. We'll add that in another PR.
- There is a minor issue: front-end tests are broken since this commit:
[80c0fc7ff1).
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,9 @@ export const companyPrefillDemoData = async (
|
||||
'employees',
|
||||
'linkedinLinkPrimaryLinkUrl',
|
||||
'position',
|
||||
'createdBySource',
|
||||
'createdByWorkspaceMemberId',
|
||||
'createdByName'
|
||||
])
|
||||
.orIgnore()
|
||||
.values(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,9 @@ export const personPrefillDemoData = async (
|
||||
avatarUrl: person.avatarUrl,
|
||||
position: index,
|
||||
companyId: companies[Math.floor(index / 2)].id,
|
||||
createdBySource: person.createdBySource,
|
||||
createdByWorkspaceMemberId: person.createdByWorkspaceMemberId,
|
||||
createdByName: person.createdByName
|
||||
}));
|
||||
|
||||
await entityManager
|
||||
@ -36,6 +39,9 @@ export const personPrefillDemoData = async (
|
||||
'avatarUrl',
|
||||
'position',
|
||||
'companyId',
|
||||
'createdBySource',
|
||||
'createdByWorkspaceMemberId',
|
||||
'createdByName',
|
||||
])
|
||||
.orIgnore()
|
||||
.values(people)
|
||||
|
||||
@ -18,6 +18,9 @@ export const companyPrefillData = async (
|
||||
'addressAddressCountry',
|
||||
'employees',
|
||||
'position',
|
||||
'createdBySource',
|
||||
'createdByWorkspaceMemberId',
|
||||
'createdByName',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
@ -32,6 +35,9 @@ export const companyPrefillData = async (
|
||||
addressAddressCountry: 'United States',
|
||||
employees: 5000,
|
||||
position: 1,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
name: 'Qonto',
|
||||
@ -44,6 +50,9 @@ export const companyPrefillData = async (
|
||||
addressAddressCountry: 'France',
|
||||
employees: 800,
|
||||
position: 2,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
name: 'Stripe',
|
||||
@ -56,6 +65,9 @@ export const companyPrefillData = async (
|
||||
addressAddressCountry: 'Ireland',
|
||||
employees: 8000,
|
||||
position: 3,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
name: 'Figma',
|
||||
@ -68,6 +80,9 @@ export const companyPrefillData = async (
|
||||
addressAddressCountry: 'United States',
|
||||
employees: 800,
|
||||
position: 4,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
name: 'Notion',
|
||||
@ -80,6 +95,9 @@ export const companyPrefillData = async (
|
||||
addressAddressCountry: 'United States',
|
||||
employees: 400,
|
||||
position: 5,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
])
|
||||
.returning('*')
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
// FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts
|
||||
export const personPrefillData = async (
|
||||
entityManager: EntityManager,
|
||||
schemaName: string,
|
||||
@ -14,6 +15,9 @@ export const personPrefillData = async (
|
||||
'email',
|
||||
'avatarUrl',
|
||||
'position',
|
||||
'createdBySource',
|
||||
'createdByWorkspaceMemberId',
|
||||
'createdByName',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
@ -25,6 +29,9 @@ export const personPrefillData = async (
|
||||
avatarUrl:
|
||||
'https://twentyhq.github.io/placeholder-images/people/image-3.png',
|
||||
position: 1,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
nameFirstName: 'Alexandre',
|
||||
@ -34,6 +41,9 @@ export const personPrefillData = async (
|
||||
avatarUrl:
|
||||
'https://twentyhq.github.io/placeholder-images/people/image-89.png',
|
||||
position: 2,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
nameFirstName: 'Patrick',
|
||||
@ -43,6 +53,9 @@ export const personPrefillData = async (
|
||||
avatarUrl:
|
||||
'https://twentyhq.github.io/placeholder-images/people/image-47.png',
|
||||
position: 3,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
nameFirstName: 'Dylan',
|
||||
@ -52,6 +65,9 @@ export const personPrefillData = async (
|
||||
avatarUrl:
|
||||
'https://twentyhq.github.io/placeholder-images/people/image-40.png',
|
||||
position: 4,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
{
|
||||
nameFirstName: 'Ivan',
|
||||
@ -61,6 +77,9 @@ export const personPrefillData = async (
|
||||
avatarUrl:
|
||||
'https://twentyhq.github.io/placeholder-images/people/image-68.png',
|
||||
position: 5,
|
||||
createdBySource: 'MANUAL',
|
||||
createdByWorkspaceMemberId: null,
|
||||
createdByName: 'System',
|
||||
},
|
||||
])
|
||||
.returning('*')
|
||||
|
||||
@ -39,7 +39,7 @@ export const companiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.accountOwner
|
||||
COMPANY_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
@ -48,7 +48,7 @@ export const companiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
COMPANY_STANDARD_FIELD_IDS.accountOwner
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
@ -57,7 +57,7 @@ export const companiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.employees
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
@ -66,10 +66,19 @@ export const companiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.linkedinLink
|
||||
COMPANY_STANDARD_FIELD_IDS.employees
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
{
|
||||
@ -77,7 +86,7 @@ export const companiesAllView = async (
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.address
|
||||
],
|
||||
position: 6,
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
|
||||
@ -32,7 +32,16 @@ export const notesAllView = async (
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
NOTE_STANDARD_FIELD_IDS.body
|
||||
],
|
||||
position: 0,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
NOTE_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
@ -41,7 +50,7 @@ export const notesAllView = async (
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 0,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
|
||||
@ -36,7 +36,7 @@ export const opportunitiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
@ -45,12 +45,21 @@ export const opportunitiesAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
|
||||
@ -39,7 +39,7 @@ export const opportunitiesByStageView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
@ -48,12 +48,21 @@ export const opportunitiesByStageView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
|
||||
@ -39,7 +39,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.company
|
||||
PERSON_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
@ -48,7 +48,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.phone
|
||||
PERSON_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
@ -57,7 +57,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
PERSON_STANDARD_FIELD_IDS.phone
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
@ -66,7 +66,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.city
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
@ -75,7 +75,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.jobTitle
|
||||
PERSON_STANDARD_FIELD_IDS.city
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
@ -84,7 +84,7 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.linkedinLink
|
||||
PERSON_STANDARD_FIELD_IDS.jobTitle
|
||||
],
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
@ -93,12 +93,21 @@ export const peopleAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.xLink
|
||||
PERSON_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
position: 8,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.xLink
|
||||
],
|
||||
position: 9,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ export const tasksAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.dueAt
|
||||
TASK_STANDARD_FIELD_IDS.createdBy
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
@ -58,7 +58,7 @@ export const tasksAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.assignee
|
||||
TASK_STANDARD_FIELD_IDS.dueAt
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
@ -67,7 +67,7 @@ export const tasksAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.body
|
||||
TASK_STANDARD_FIELD_IDS.assignee
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
@ -76,12 +76,21 @@ export const tasksAllView = async (
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
TASK_STANDARD_FIELD_IDS.body
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
/*
|
||||
TODO: Add later, since we don't have real-time it probably doesn't work well?
|
||||
{
|
||||
|
||||
@ -1,26 +1,29 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { render } from '@react-email/render';
|
||||
import { In } from 'typeorm';
|
||||
import {
|
||||
CleanInactiveWorkspaceEmail,
|
||||
DeleteInactiveWorkspaceEmail,
|
||||
} from 'twenty-emails';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { CleanInactiveWorkspacesCommandOptions } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-inactive-workspaces.command';
|
||||
import { getDryRunLogHeader } from 'src/utils/get-dry-run-log-header';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
|
||||
const MILLISECONDS_IN_ONE_DAY = 1000 * 3600 * 24;
|
||||
|
||||
@ -36,6 +39,8 @@ export class CleanInactiveWorkspaceJob {
|
||||
private readonly inactiveDaysBeforeEmail;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
@ -94,8 +99,20 @@ export class CleanInactiveWorkspaceJob {
|
||||
daysSinceInactive: number,
|
||||
isDryRun: boolean,
|
||||
) {
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: dataSource.workspaceId },
|
||||
});
|
||||
|
||||
if (!workspace) {
|
||||
this.logger.error(
|
||||
`Workspace with id ${dataSource.workspaceId} not found in database`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceMembers =
|
||||
await this.userService.loadWorkspaceMembers(dataSource);
|
||||
await this.userService.loadWorkspaceMembers(workspace);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSource);
|
||||
@ -110,7 +127,7 @@ export class CleanInactiveWorkspaceJob {
|
||||
`${getDryRunLogHeader(isDryRun)}Sending workspace ${
|
||||
dataSource.workspaceId
|
||||
} inactive since ${daysSinceInactive} days emails to users ['${workspaceMembers
|
||||
.map((workspaceUser) => workspaceUser.email)
|
||||
.map((workspaceUser) => workspaceUser.userEmail)
|
||||
.join(', ')}']`,
|
||||
);
|
||||
|
||||
@ -121,7 +138,7 @@ export class CleanInactiveWorkspaceJob {
|
||||
workspaceMembers.forEach((workspaceMember) => {
|
||||
const emailData = {
|
||||
daysLeft: this.inactiveDaysBeforeDelete - daysSinceInactive,
|
||||
userName: `${workspaceMember.nameFirstName} ${workspaceMember.nameLastName}`,
|
||||
userName: `${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`,
|
||||
workspaceDisplayName: `${displayName}`,
|
||||
};
|
||||
const emailTemplate = CleanInactiveWorkspaceEmail(emailData);
|
||||
@ -133,7 +150,7 @@ export class CleanInactiveWorkspaceJob {
|
||||
});
|
||||
|
||||
this.emailService.send({
|
||||
to: workspaceMember.email,
|
||||
to: workspaceMember.userEmail,
|
||||
bcc: this.environmentService.get('EMAIL_SYSTEM_ADDRESS'),
|
||||
from: `${this.environmentService.get(
|
||||
'EMAIL_FROM_NAME',
|
||||
|
||||
@ -126,6 +126,7 @@ export const COMPANY_STANDARD_FIELD_IDS = {
|
||||
annualRecurringRevenue: '20202020-602a-495c-9776-f5d5b11d227b',
|
||||
idealCustomerProfile: '20202020-ba6b-438a-8213-2c5ba28d76a2',
|
||||
position: '20202020-9b4e-462b-991d-a0ee33326454',
|
||||
createdBy: '20202020-fabc-451d-ab7d-412170916baa',
|
||||
people: '20202020-3213-4ddf-9494-6422bcff8d7c',
|
||||
accountOwner: '20202020-95b8-4e10-9881-edb5d4765f9d',
|
||||
activityTargets: '20202020-c2a5-4c9b-9d9a-582bcd57fbc8',
|
||||
@ -267,6 +268,7 @@ export const NOTE_STANDARD_FIELD_IDS = {
|
||||
position: '20202020-368d-4dc2-943f-ed8a49c7fdfb',
|
||||
title: '20202020-faeb-4c76-8ba6-ccbb0b4a965f',
|
||||
body: '20202020-e63d-4e70-95be-a78cd9abe7ef',
|
||||
createdBy: '20202020-0d79-4e21-ab77-5a394eff97be',
|
||||
noteTargets: '20202020-1f25-43fe-8b00-af212fdde823',
|
||||
attachments: '20202020-4986-4c92-bf19-39934b149b16',
|
||||
timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce',
|
||||
@ -287,6 +289,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = {
|
||||
probabilityDeprecated: '20202020-69d4-45f3-9703-690b09fafcf0',
|
||||
stage: '20202020-6f76-477d-8551-28cd65b2b4b9',
|
||||
position: '20202020-806d-493a-bbc6-6313e62958e2',
|
||||
createdBy: '20202020-a63e-4a62-8e63-42a51828f831',
|
||||
pointOfContact: '20202020-8dfb-42fc-92b6-01afb759ed16',
|
||||
company: '20202020-cbac-457e-b565-adece5fc815f',
|
||||
favorites: '20202020-a1c2-4500-aaae-83ba8a0e827a',
|
||||
@ -307,6 +310,7 @@ export const PERSON_STANDARD_FIELD_IDS = {
|
||||
city: '20202020-5243-4ffb-afc5-2c675da41346',
|
||||
avatarUrl: '20202020-b8a6-40df-961c-373dc5d2ec21',
|
||||
position: '20202020-fcd5-4231-aff5-fff583eaa0b1',
|
||||
createdBy: '20202020-f6ab-4d98-af24-a3d5b664148a',
|
||||
company: '20202020-e2f3-448e-b34c-2d625f0025fd',
|
||||
pointOfContactForOpportunities: '20202020-911b-4a7d-b67b-918aa9a5b33a',
|
||||
activityTargets: '20202020-dee7-4b7f-b50a-1f50bd3be452',
|
||||
@ -325,6 +329,7 @@ export const TASK_STANDARD_FIELD_IDS = {
|
||||
body: '20202020-ce13-43f4-8821-69388fe1fd26',
|
||||
dueAt: '20202020-fd99-40da-951b-4cb9a352fce3',
|
||||
status: '20202020-70bc-48f9-89c5-6aa730b151e0',
|
||||
createdBy: '20202020-1a04-48ab-a567-576965ae5387',
|
||||
taskTargets: '20202020-de9c-4d0e-a452-713d4a3e5fc7',
|
||||
attachments: '20202020-794d-4783-a8ff-cecdb15be139',
|
||||
assignee: '20202020-065a-4f42-a906-e20422c1753f',
|
||||
@ -433,6 +438,7 @@ export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = {
|
||||
export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
|
||||
name: '20202020-ba07-4ffd-ba63-009491f5749c',
|
||||
position: '20202020-c2bd-4e16-bb9a-c8b0411bf49d',
|
||||
createdBy: '20202020-be0e-4971-865b-32ca87cbb315',
|
||||
activityTargets: '20202020-7f42-40ae-b96c-c8a61acc83bf',
|
||||
noteTargets: '20202020-01fd-4f37-99dc-9427a444018a',
|
||||
taskTargets: '20202020-0860-4566-b865-bff3c626c303',
|
||||
|
||||
Reference in New Issue
Block a user