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

New variable relation filter syntax + upgrade command #9765

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,11 @@ import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { RelationFilterValue } from '@/views/view-filter-value/types/RelationFilterValue';
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { isDefined } from 'twenty-shared';
import { IconUserCircle } from 'twenty-ui';
import { v4 } from 'uuid';

export const EMPTY_FILTER_VALUE: string = JSON.stringify({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: [],
} satisfies RelationFilterValue);

export const MAX_RECORDS_TO_DISPLAY = 3;

type ObjectFilterDropdownRecordSelectProps = {
Expand Down Expand Up @@ -69,14 +62,13 @@ export const ObjectFilterDropdownRecordSelect = ({
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId);

const { isCurrentWorkspaceMemberSelected } = jsonRelationFilterValueSchema
.catch({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: simpleRelationFilterValueSchema.parse(
selectedFilter?.value,
),
})
.parse(selectedFilter?.value);
const parsedFilterValue = relationFilterValueSchema.parse(
selectedFilter?.value,
);

const isCurrentWorkspaceMemberSelected = parsedFilterValue.includes(
'{{CURRENT_WORKSPACE_MEMBER}}',
);

if (!isDefined(fieldMetadataItemUsedInFilterDropdown)) {
throw new Error('fieldMetadataItemUsedInFilterDropdown is not defined');
Expand Down Expand Up @@ -188,11 +180,14 @@ export const ObjectFilterDropdownRecordSelect = ({
) {
const newFilterValue =
newSelectedRecordIds.length > 0 || newIsCurrentWorkspaceMemberSelected
? JSON.stringify({
isCurrentWorkspaceMemberSelected:
newIsCurrentWorkspaceMemberSelected,
selectedRecordIds: newSelectedRecordIds,
} satisfies RelationFilterValue)
? JSON.stringify(
[
...newSelectedRecordIds,
newIsCurrentWorkspaceMemberSelected
? '{{CURRENT_WORKSPACE_MEMBER}}'
: undefined,
].filter(isDefined),
)
: '';

const viewFilter =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID =
'CURRENT_WORKSPACE_MEMBER';
'{{CURRENT_WORKSPACE_MEMBER}}';
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import { resolveDateViewFilterValue } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue';
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
import { z } from 'zod';

Expand Down Expand Up @@ -306,22 +305,13 @@ export const computeFilterRecordGqlOperationFilter = ({
);
}
case 'RELATION': {
const { isCurrentWorkspaceMemberSelected, selectedRecordIds } =
jsonRelationFilterValueSchema
.catch({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: simpleRelationFilterValueSchema.parse(
filter.value,
),
})
.parse(filter.value);

const recordIds = isCurrentWorkspaceMemberSelected
? [
...selectedRecordIds,
filterValueDependencies.currentWorkspaceMemberId,
]
: selectedRecordIds;
const parsedFilterValue = relationFilterValueSchema.parse(filter.value);

const recordIds = parsedFilterValue.map((item) =>
item === '{{CURRENT_WORKSPACE_MEMBER}}'
? filterValueDependencies.currentWorkspaceMemberId
: item,
);

if (recordIds.length === 0) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { isDefined } from 'twenty-shared';

type ViewBarFilterEffectProps = {
Expand Down Expand Up @@ -45,14 +44,16 @@ export const ViewBarFilterEffect = ({
filter.fieldMetadataId === fieldMetadataItemUsedInDropdown?.id,
);

const { selectedRecordIds } = jsonRelationFilterValueSchema
.catch({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: simpleRelationFilterValueSchema.parse(
viewFilterUsedInDropdown?.value,
),
})
.parse(viewFilterUsedInDropdown?.value);
const parsedFilterValue = relationFilterValueSchema.parse(
viewFilterUsedInDropdown?.value,
);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially hoping we could do something a bit more elegant that replaces {{ }} at a higher level for all filter types, but it's maybe more pragmatic to go for that! Fine with me. cc @charlesBochet again

const isVariableItem = (item: string) =>
item.startsWith('{{') && item.endsWith('}}');

const selectedRecordIds = parsedFilterValue.filter(
(item) => !isVariableItem(item),
);

setObjectFilterDropdownSelectedRecordIds(selectedRecordIds);
} else if (
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { z } from 'zod';

const relationFilterValueItemSchema = z.union([
z.string().uuid(),
z.literal('{{CURRENT_WORKSPACE_MEMBER}}'),
]);

const relationFilterValueArraySchema = z.array(relationFilterValueItemSchema);

export const relationFilterValueSchema = z
.string()
.transform((value, ctx) => {
if (value === '') {
return [];
}

try {
return JSON.parse(value);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: (error as Error).message,
});
return z.NEVER;
}
})
.pipe(relationFilterValueArraySchema);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { InjectRepository } from '@nestjs/typeorm';

import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared';
import { In, Repository } from 'typeorm';
import { z } from 'zod';

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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-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 { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';

const relationFilterValueSchemaObject = z.object({
isCurrentWorkspaceMemberSelected: z.boolean().optional(),
selectedRecordIds: z.array(z.string()),
});

const jsonRelationFilterValueSchema = z
.string()
.transform((value, ctx) => {
try {
return JSON.parse(value);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: (error as Error).message,
});

return z.NEVER;
}
})
.pipe(relationFilterValueSchemaObject);

@Command({
name: 'upgrade-0.42:standardize-variable-view-filter-syntax',
description: 'Standardize variable view filter syntax',
})
export class StandardizeVariableViewFilterSyntaxCommand extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository);
}

async executeActiveWorkspacesCommand(
_passedParam: string[],
options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to standardize view filter syntax for relation filters',
);

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

let workspaceIterator = 1;

for (const workspaceId of workspaceIds) {
this.logger.log(
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`,
);

try {
const viewFilterRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
'viewFilter',
false,
);

const relationFieldMetadata = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: FieldMetadataType.RELATION,
},
});

const relationFieldMetadataIds = relationFieldMetadata.map(
(fieldMetadata) => fieldMetadata.id,
);

const relationViewFilters = await viewFilterRepository.find({
where: {
fieldMetadataId: In(relationFieldMetadataIds),
},
});

for (const relationViewFilter of relationViewFilters) {
await viewFilterRepository.update(relationViewFilter.id, {
...relationViewFilter,
value: this.convertRelationViewFilterValue(
relationViewFilter.value,
),
});
}

await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);

await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);

workspaceIterator++;
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
} catch (error) {
this.logger.error(
`Error running command for workspace ${workspaceId}: ${error}`,
);
}
}
this.logger.log(chalk.green(`Command completed!`));
}

private convertRelationViewFilterValue(value: string): string {
const jsonRelationFilterValueResult =
jsonRelationFilterValueSchema.safeParse(value);

if (!jsonRelationFilterValueResult.success) {
return value;
}

const { selectedRecordIds, isCurrentWorkspaceMemberSelected } =
jsonRelationFilterValueResult.data;

if (isCurrentWorkspaceMemberSelected) {
return JSON.stringify([
...selectedRecordIds,
'{{CURRENT_WORKSPACE_MEMBER}}',
]);
}

return JSON.stringify(selectedRecordIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BaseCommandOptions } from 'src/database/commands/base.command';
import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command';
import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field';
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
import { StandardizeVariableViewFilterSyntaxCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardize-variable-view-filter-syntax.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';

@Command({
Expand All @@ -21,6 +22,7 @@ export class UpgradeTo0_42Command extends ActiveWorkspacesCommandRunner {
private readonly migrateRichTextFieldCommand: MigrateRichTextFieldCommand,
private readonly fixBodyV2ViewFieldPositionCommand: FixBodyV2ViewFieldPositionCommand,
private readonly limitAmountOfViewFieldCommand: LimitAmountOfViewFieldCommand,
private readonly standardizeVariableViewFilterSyntaxCommand: StandardizeVariableViewFilterSyntaxCommand,
) {
super(workspaceRepository);
}
Expand All @@ -32,6 +34,12 @@ export class UpgradeTo0_42Command extends ActiveWorkspacesCommandRunner {
): Promise<void> {
this.logger.log('Running command to upgrade to 0.42');

await this.standardizeVariableViewFilterSyntaxCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);

await this.migrateRichTextFieldCommand.executeActiveWorkspacesCommand(
passedParam,
options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command';
import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field';
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
import { StandardizeVariableViewFilterSyntaxCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardize-variable-view-filter-syntax.command';
import { UpgradeTo0_42Command } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.command';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
Expand Down Expand Up @@ -33,6 +34,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
MigrateRichTextFieldCommand,
FixBodyV2ViewFieldPositionCommand,
LimitAmountOfViewFieldCommand,
StandardizeVariableViewFilterSyntaxCommand,
],
})
export class UpgradeTo0_42CommandModule {}
Loading