Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a7d878a85 | |||
| e83ed53d79 | |||
| a42d7baef4 |
+54
@@ -0,0 +1,54 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { computeRecordGroupOptionsFilter } from '@/object-record/record-group/utils/computeRecordGroupOptionsFilter';
|
||||
|
||||
const mockFieldMetadata = {
|
||||
id: 'field-1',
|
||||
name: 'status',
|
||||
} as FieldMetadataItem;
|
||||
|
||||
describe('computeRecordGroupOptionsFilter', () => {
|
||||
it('should return empty object when recordGroupFieldMetadata is undefined', () => {
|
||||
const result = computeRecordGroupOptionsFilter({
|
||||
recordGroupFieldMetadata: undefined,
|
||||
recordGroupValues: ['value1', 'value2'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object when recordGroupFieldMetadata is null', () => {
|
||||
const result = computeRecordGroupOptionsFilter({
|
||||
recordGroupFieldMetadata: null,
|
||||
recordGroupValues: ['value1', 'value2'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return simple IN filter when no null values present', () => {
|
||||
const result = computeRecordGroupOptionsFilter({
|
||||
recordGroupFieldMetadata: mockFieldMetadata,
|
||||
recordGroupValues: ['value1', 'value2', 'value3'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
status: {
|
||||
in: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return OR filter with IS NULL when null value is present', () => {
|
||||
const result = computeRecordGroupOptionsFilter({
|
||||
recordGroupFieldMetadata: mockFieldMetadata,
|
||||
recordGroupValues: ['value1', null, 'value2'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
or: [
|
||||
{ status: { is: 'NULL' } },
|
||||
{ status: { in: ['value1', 'value2'] } },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
|
||||
export const computeRecordGroupOptionsFilter = ({
|
||||
recordGroupFieldMetadata,
|
||||
recordGroupValues,
|
||||
}: {
|
||||
recordGroupFieldMetadata: FieldMetadataItem | null | undefined;
|
||||
recordGroupValues: RecordGroupDefinition['value'][];
|
||||
}): RecordGqlOperationFilter => {
|
||||
if (!isDefined(recordGroupFieldMetadata) || recordGroupValues.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const fieldName = recordGroupFieldMetadata.name;
|
||||
const hasNullValue = recordGroupValues.some(isNull);
|
||||
const nonNullValues = recordGroupValues.filter(
|
||||
(value): value is NonNullable<typeof value> => !isNull(value),
|
||||
);
|
||||
|
||||
return hasNullValue
|
||||
? {
|
||||
or: [
|
||||
{ [fieldName]: { is: 'NULL' } },
|
||||
...(nonNullValues.length > 0
|
||||
? [{ [fieldName]: { in: nonNullValues } }]
|
||||
: []),
|
||||
],
|
||||
}
|
||||
: nonNullValues.length > 0
|
||||
? {
|
||||
[fieldName]: { in: recordGroupValues },
|
||||
}
|
||||
: {};
|
||||
};
|
||||
+5
-8
@@ -5,6 +5,7 @@ import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector';
|
||||
import { computeRecordGroupOptionsFilter } from '@/object-record/record-group/utils/computeRecordGroupOptionsFilter';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
@@ -12,7 +13,6 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
|
||||
import {
|
||||
combineFilters,
|
||||
computeRecordGqlOperationFilter,
|
||||
isDefined,
|
||||
turnAnyFieldFilterIntoRecordGqlFilter,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
@@ -73,13 +73,10 @@ export const useRecordIndexGroupCommonQueryVariables = () => {
|
||||
(recordGroupDefinition) => recordGroupDefinition.value,
|
||||
);
|
||||
|
||||
const recordGroupOptionsFilter = isDefined(recordGroupFieldMetadata)
|
||||
? {
|
||||
[recordGroupFieldMetadata.name]: {
|
||||
in: recordGroupValues,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
const recordGroupOptionsFilter = computeRecordGroupOptionsFilter({
|
||||
recordGroupFieldMetadata,
|
||||
recordGroupValues,
|
||||
});
|
||||
|
||||
const combinedFilters = combineFilters([
|
||||
anyFieldFilter,
|
||||
|
||||
+3
-73
@@ -4,16 +4,9 @@ import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/ho
|
||||
|
||||
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
|
||||
import { useTriggerFetchPages } from '@/object-record/record-table/virtualization/hooks/useTriggerFetchPages';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilyState';
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
@@ -26,7 +19,6 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
});
|
||||
|
||||
const params = useFindManyRecordIndexTableParams(objectNameSingular);
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
|
||||
// TODO: we could optimize this by using an aggregate or using only id: true in recordGqlFields
|
||||
const recordGqlFields = useRecordsFieldVisibleGqlFields({
|
||||
@@ -49,82 +41,20 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
dataPagesLoadedComponentState,
|
||||
);
|
||||
|
||||
const lastScrollPositionCallbackState = useRecoilComponentCallbackState(
|
||||
lastScrollPositionComponentState,
|
||||
);
|
||||
|
||||
const recordIdByRealIndexCallbackState =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
);
|
||||
|
||||
const dataLoadingStatusByRealIndexCallbackState =
|
||||
useRecoilComponentCallbackState(
|
||||
dataLoadingStatusByRealIndexComponentFamilyState,
|
||||
);
|
||||
|
||||
const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages();
|
||||
|
||||
const resetVirtualization = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
({ set }) =>
|
||||
async () => {
|
||||
const { totalCount } = await findManyRecordsLazy();
|
||||
|
||||
const tableScrollWrapperHeight =
|
||||
scrollWrapperHTMLElement?.clientHeight ?? 0;
|
||||
|
||||
const lastScrollPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
lastScrollPositionCallbackState,
|
||||
);
|
||||
|
||||
const totalNumberOfRecordsToVirtualize =
|
||||
getSnapshotValue(
|
||||
snapshot,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
) ?? 0;
|
||||
|
||||
const {
|
||||
firstRealIndexInOverscanWindow,
|
||||
lastRealIndexInOverscanWindow,
|
||||
} = getVirtualizationOverscanWindow(
|
||||
lastScrollPosition,
|
||||
tableScrollWrapperHeight,
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
);
|
||||
|
||||
for (let i = 0; i < totalNumberOfRecordsToVirtualize; i++) {
|
||||
const indexIsInOverscanWindow =
|
||||
i >= firstRealIndexInOverscanWindow &&
|
||||
i <= lastRealIndexInOverscanWindow;
|
||||
|
||||
if (!indexIsInOverscanWindow) {
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState({
|
||||
realIndex: i,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
recordIdByRealIndexCallbackState({
|
||||
realIndex: i,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
set(dataPagesLoadedCallbackState, []);
|
||||
set(totalNumberOfRecordsToVirtualizeCallbackState, totalCount);
|
||||
},
|
||||
[
|
||||
findManyRecordsLazy,
|
||||
scrollWrapperHTMLElement?.clientHeight,
|
||||
lastScrollPositionCallbackState,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
dataPagesLoadedCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
findManyRecordsLazy,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies';
|
||||
import { prefillCoreViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views';
|
||||
import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people';
|
||||
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows';
|
||||
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
|
||||
import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service';
|
||||
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
|
||||
@@ -52,7 +54,6 @@ export class WorkspaceManagerService {
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@@ -103,6 +104,11 @@ export class WorkspaceManagerService {
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.prefillCreatedWorkspaceRecords({
|
||||
workspaceId,
|
||||
schemaName,
|
||||
});
|
||||
} else {
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId,
|
||||
@@ -148,6 +154,56 @@ export class WorkspaceManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
private async prefillCreatedWorkspaceRecords({
|
||||
workspaceId,
|
||||
schemaName,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
schemaName: string;
|
||||
}): Promise<void> {
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
|
||||
try {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
await prefillCompanies(queryRunner.manager, schemaName);
|
||||
|
||||
await prefillPeople(queryRunner.manager, schemaName);
|
||||
|
||||
await prefillWorkflows(
|
||||
queryRunner.manager,
|
||||
schemaName,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
try {
|
||||
await queryRunner.rollbackTransaction();
|
||||
} catch (rollbackError) {
|
||||
this.logger.error(
|
||||
`Failed to rollback prefill transaction: ${rollbackError.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
private async prefillWorkspaceWithStandardObjectsRecords({
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user