Allow DATE_TIME IS operand to filter on a whole day (#17529)
Fixes: https://github.com/twentyhq/core-team-issues/issues/2027 We've replaced the DATE_TIME picker with DATE picker, and changed the logic to filter for complete day period. https://github.com/user-attachments/assets/ba7e1078-bab3-4c62-a803-d6a851f14b7d --------- Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@@ -1480,6 +1480,7 @@ export enum FeatureFlagKey {
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_CORE_PICTURE_MIGRATED = 'IS_CORE_PICTURE_MIGRATED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED = 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_FILES_FIELD_MIGRATED = 'IS_FILES_FIELD_MIGRATED',
|
||||
|
||||
+11
-1
@@ -19,6 +19,7 @@ import {
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||
import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter';
|
||||
import { WORKFLOW_TIMEZONE } from '@/workflow/constants/WorkflowTimeZone';
|
||||
import { isObject, isString } from '@sniptt/guards';
|
||||
@@ -62,6 +63,10 @@ export const AdvancedFilterCommandMenuValueFormInput = ({
|
||||
const { applyObjectFilterDropdownFilterValue } =
|
||||
useApplyObjectFilterDropdownFilterValue();
|
||||
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isWholeDayFilterEnabled =
|
||||
featureFlags.IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED ?? false;
|
||||
|
||||
const handleChange = (newValue: JsonValue) => {
|
||||
if (isString(newValue)) {
|
||||
applyObjectFilterDropdownFilterValue(newValue);
|
||||
@@ -178,7 +183,12 @@ export const AdvancedFilterCommandMenuValueFormInput = ({
|
||||
}
|
||||
|
||||
const field = {
|
||||
type: recordFilter.type as FieldMetadataType,
|
||||
type:
|
||||
isWholeDayFilterEnabled === true &&
|
||||
recordFilter.type === FieldMetadataType.DATE_TIME &&
|
||||
recordFilter.operand === RecordFilterOperand.IS
|
||||
? FieldMetadataType.DATE
|
||||
: (recordFilter.type as FieldMetadataType),
|
||||
label: '',
|
||||
metadata: fieldDefinition?.metadata as FieldMetadata,
|
||||
};
|
||||
|
||||
+9
-3
@@ -38,7 +38,12 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
useApplyObjectFilterDropdownFilterValue();
|
||||
|
||||
const handleAbsoluteDateChange = (newPlainDate: string | null) => {
|
||||
const newFilterValue = newPlainDate ?? '';
|
||||
if (!isDefined(newPlainDate)) {
|
||||
applyObjectFilterDropdownFilterValue('', '');
|
||||
return;
|
||||
}
|
||||
|
||||
const newFilterValue = newPlainDate;
|
||||
|
||||
// TODO: remove this and use getDisplayValue instead
|
||||
const formattedDate = formatDateString({
|
||||
@@ -91,6 +96,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
? handleRelativeDateChange(null)
|
||||
: handleAbsoluteDateChange(null);
|
||||
};
|
||||
|
||||
const resolvedValue = objectFilterDropdownCurrentRecordFilter
|
||||
? resolveDateFilter(objectFilterDropdownCurrentRecordFilter)
|
||||
: null;
|
||||
@@ -100,7 +106,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
const plainDateValue =
|
||||
const safePlainDateValue: string | undefined =
|
||||
resolvedValue && typeof resolvedValue === 'string'
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
@@ -110,7 +116,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
instanceId={`object-filter-dropdown-date-input`}
|
||||
relativeDate={relativeDate}
|
||||
isRelative={isRelativeOperand}
|
||||
plainDateString={plainDateValue ?? null}
|
||||
plainDateString={safePlainDateValue ?? null}
|
||||
onChange={handleAbsoluteDateChange}
|
||||
onRelativeDateChange={handleRelativeDateChange}
|
||||
onClear={handleClear}
|
||||
|
||||
+17
@@ -5,6 +5,7 @@ import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-d
|
||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
@@ -28,6 +29,10 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
filterDropdownId,
|
||||
recordFilterId,
|
||||
}: ObjectFilterDropdownFilterInputProps) => {
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isWholeDayFilterEnabled =
|
||||
featureFlags.IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED ?? false;
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValue(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
@@ -76,6 +81,18 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
</>
|
||||
);
|
||||
} else if (filterType === 'DATE_TIME') {
|
||||
if (
|
||||
isWholeDayFilterEnabled &&
|
||||
selectedOperandInDropdown === ViewFilterOperand.IS
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownDateInput />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
|
||||
+76
-10
@@ -1,3 +1,6 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
import { useUpsertObjectFilterDropdownCurrentFilter } from '@/object-record/object-filter-dropdown/hooks/useUpsertObjectFilterDropdownCurrentFilter';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
|
||||
@@ -7,14 +10,12 @@ import { useGetRelativeDateFilterWithUserTimezone } from '@/object-record/record
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
|
||||
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
import { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from 'twenty-shared/constants';
|
||||
|
||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||
import { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from 'twenty-shared/constants';
|
||||
import {
|
||||
isDefined,
|
||||
relativeDateFilterStringifiedSchema,
|
||||
@@ -47,6 +48,10 @@ export const useApplyObjectFilterDropdownOperand = () => {
|
||||
const { getRelativeDateFilterWithUserTimezone } =
|
||||
useGetRelativeDateFilterWithUserTimezone();
|
||||
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isWholeDayFilterEnabled =
|
||||
featureFlags.IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED ?? false;
|
||||
|
||||
const applyObjectFilterDropdownOperand = (
|
||||
newOperand: RecordFilterOperand,
|
||||
) => {
|
||||
@@ -106,7 +111,24 @@ export const useApplyObjectFilterDropdownOperand = () => {
|
||||
recordFilterToUpsert.value,
|
||||
);
|
||||
|
||||
if (filterValueIsEmpty || isStillRelativeFilterValue.success) {
|
||||
const previousOperand =
|
||||
objectFilterDropdownCurrentRecordFilter?.operand;
|
||||
|
||||
const isDateTimeOperandFormatChange =
|
||||
recordFilterToUpsert.type === 'DATE_TIME' &&
|
||||
!filterValueIsEmpty &&
|
||||
!isStillRelativeFilterValue.success &&
|
||||
(previousOperand === RecordFilterOperand.IS ||
|
||||
newOperand === RecordFilterOperand.IS);
|
||||
|
||||
if (isDateTimeOperandFormatChange) {
|
||||
recordFilterToUpsert.value = convertDateTimeFilterValue(
|
||||
recordFilterToUpsert.value,
|
||||
newOperand,
|
||||
userTimezone,
|
||||
isWholeDayFilterEnabled,
|
||||
);
|
||||
} else if (filterValueIsEmpty || isStillRelativeFilterValue.success) {
|
||||
const zonedDateToUse = Temporal.Now.zonedDateTimeISO(userTimezone);
|
||||
|
||||
if (recordFilterToUpsert.type === 'DATE') {
|
||||
@@ -116,11 +138,18 @@ export const useApplyObjectFilterDropdownOperand = () => {
|
||||
|
||||
recordFilterToUpsert.value = initialNowDateFilterValue;
|
||||
} else {
|
||||
const initialNowDateTimeFilterValue = zonedDateToUse
|
||||
.toInstant()
|
||||
.toString();
|
||||
|
||||
recordFilterToUpsert.value = initialNowDateTimeFilterValue;
|
||||
if (
|
||||
newOperand === RecordFilterOperand.IS &&
|
||||
isWholeDayFilterEnabled
|
||||
) {
|
||||
recordFilterToUpsert.value = zonedDateToUse
|
||||
.toPlainDate()
|
||||
.toString();
|
||||
} else {
|
||||
recordFilterToUpsert.value = zonedDateToUse
|
||||
.toInstant()
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,3 +166,40 @@ export const useApplyObjectFilterDropdownOperand = () => {
|
||||
applyObjectFilterDropdownOperand,
|
||||
};
|
||||
};
|
||||
|
||||
const convertDateTimeFilterValue = (
|
||||
currentValue: string,
|
||||
targetOperand: RecordFilterOperand,
|
||||
userTimezone: string,
|
||||
isWholeDayFilterEnabled = false,
|
||||
): string => {
|
||||
const zonedDateToUse = Temporal.Now.zonedDateTimeISO(userTimezone);
|
||||
|
||||
if (targetOperand === RecordFilterOperand.IS) {
|
||||
try {
|
||||
const existingZoned = currentValue.includes('T')
|
||||
? Temporal.Instant.from(currentValue).toZonedDateTimeISO(userTimezone)
|
||||
: Temporal.PlainDate.from(currentValue).toZonedDateTime(userTimezone);
|
||||
|
||||
if (isWholeDayFilterEnabled) {
|
||||
return existingZoned.toPlainDate().toString();
|
||||
} else {
|
||||
return existingZoned.toInstant().toString();
|
||||
}
|
||||
} catch {
|
||||
return zonedDateToUse.toPlainDate().toString();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const existingPlainDate = Temporal.PlainDate.from(currentValue);
|
||||
const currentTime = zonedDateToUse.toPlainTime();
|
||||
const zonedFromPlain = existingPlainDate.toZonedDateTime({
|
||||
timeZone: userTimezone,
|
||||
plainTime: currentTime,
|
||||
});
|
||||
return zonedFromPlain.toInstant().toString();
|
||||
} catch {
|
||||
return zonedDateToUse.toInstant().toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+18
-1
@@ -1,9 +1,12 @@
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
import { useGetDateFilterDisplayValue } from '@/object-record/object-filter-dropdown/hooks/useGetDateFilterDisplayValue';
|
||||
import { useGetDateTimeFilterDisplayValue } from '@/object-record/object-filter-dropdown/hooks/useGetDateTimeFilterDisplayValue';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
|
||||
|
||||
import { type FilterableAndTSVectorFieldType } from 'twenty-shared/types';
|
||||
|
||||
const activeDatePickerOperands = [
|
||||
@@ -16,6 +19,9 @@ export const useGetInitialFilterValue = () => {
|
||||
const { userTimezone } = useUserTimezone();
|
||||
const { getDateFilterDisplayValue } = useGetDateFilterDisplayValue();
|
||||
const { getDateTimeFilterDisplayValue } = useGetDateTimeFilterDisplayValue();
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isWholeDayFilterEnabled =
|
||||
featureFlags.IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED ?? false;
|
||||
|
||||
const getInitialFilterValue = (
|
||||
newType: FilterableAndTSVectorFieldType,
|
||||
@@ -44,6 +50,17 @@ export const useGetInitialFilterValue = () => {
|
||||
alreadyExistingZonedDateTime ??
|
||||
Temporal.Now.zonedDateTimeISO(userTimezone);
|
||||
|
||||
if (
|
||||
isWholeDayFilterEnabled === true &&
|
||||
newOperand === RecordFilterOperand.IS
|
||||
) {
|
||||
const value = referenceDate.toPlainDate().toString();
|
||||
|
||||
const { displayValue } = getDateFilterDisplayValue(referenceDate);
|
||||
|
||||
return { value, displayValue };
|
||||
}
|
||||
|
||||
const value = referenceDate.toInstant().toString();
|
||||
|
||||
const { displayValue } = getDateTimeFilterDisplayValue(referenceDate);
|
||||
|
||||
+21
-3
@@ -1,3 +1,6 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
|
||||
import { useGetDateFilterDisplayValue } from '@/object-record/object-filter-dropdown/hooks/useGetDateFilterDisplayValue';
|
||||
import { useGetDateTimeFilterDisplayValue } from '@/object-record/object-filter-dropdown/hooks/useGetDateTimeFilterDisplayValue';
|
||||
@@ -7,8 +10,7 @@ import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordF
|
||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
|
||||
import { getTimezoneAbbreviationForZonedDateTime } from '@/ui/input/components/internal/date/utils/getTimeZoneAbbreviationForZonedDateTime';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
@@ -87,7 +89,23 @@ export const useGetRecordFilterDisplayValue = () => {
|
||||
}
|
||||
} else if (recordFilter.type === 'DATE_TIME') {
|
||||
switch (recordFilter.operand) {
|
||||
case RecordFilterOperand.IS:
|
||||
case RecordFilterOperand.IS: {
|
||||
if (!isNonEmptyString(recordFilter.value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const zonedDateTime = recordFilter.value.includes('T')
|
||||
? Temporal.Instant.from(recordFilter.value).toZonedDateTimeISO(
|
||||
userTimezone,
|
||||
)
|
||||
: Temporal.PlainDate.from(recordFilter.value).toZonedDateTime(
|
||||
userTimezone,
|
||||
);
|
||||
|
||||
const { displayValue } = getDateFilterDisplayValue(zonedDateTime);
|
||||
|
||||
return `${displayValue}`;
|
||||
}
|
||||
case RecordFilterOperand.IS_AFTER:
|
||||
case RecordFilterOperand.IS_BEFORE: {
|
||||
if (!isNonEmptyString(recordFilter.value)) {
|
||||
|
||||
+4
-4
@@ -1024,9 +1024,9 @@ describe('should work as expected for the different field types', () => {
|
||||
|
||||
const dateFilterIs: RecordFilter = {
|
||||
id: 'company-date-filter-is',
|
||||
value: '2024-09-17T20:46:58.922Z',
|
||||
value: '2024-09-17',
|
||||
fieldMetadataId: companyMockDateFieldMetadataId?.id,
|
||||
displayValue: '2024-09-17T20:46:58.922Z',
|
||||
displayValue: '2024-09-17',
|
||||
operand: ViewFilterOperand.IS,
|
||||
label: 'Created At',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
@@ -1081,12 +1081,12 @@ describe('should work as expected for the different field types', () => {
|
||||
and: [
|
||||
{
|
||||
createdAt: {
|
||||
lt: '2024-09-17T20:47:00Z',
|
||||
gte: '2024-09-16T22:00:00Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
createdAt: {
|
||||
gte: '2024-09-17T20:46:00Z',
|
||||
lt: '2024-09-17T22:00:00Z',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export enum FeatureFlagKey {
|
||||
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED = 'IS_NAVIGATION_MENU_ITEM_ENABLED',
|
||||
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED = 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED',
|
||||
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED = 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const DEFAULT_TIMEZONE = 'UTC';
|
||||
+7
@@ -12,6 +12,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
|
||||
import { ViewQueryParamsService } from 'src/engine/metadata-modules/view/services/view-query-params.service';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
describe('ViewQueryParamsService', () => {
|
||||
let viewQueryParamsService: ViewQueryParamsService;
|
||||
@@ -73,6 +74,12 @@ describe('ViewQueryParamsService', () => {
|
||||
getOrRecomputeManyOrAllFlatEntityMaps: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+35
-1
@@ -20,7 +20,10 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
|
||||
import { DEFAULT_TIMEZONE } from 'src/engine/metadata-modules/view/constants/default-timezone.constant';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type ViewQueryParams = {
|
||||
objectNameSingular: string;
|
||||
@@ -35,6 +38,7 @@ export class ViewQueryParamsService {
|
||||
constructor(
|
||||
private readonly viewService: ViewService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
) {}
|
||||
|
||||
async resolveViewToQueryParams(
|
||||
@@ -61,6 +65,11 @@ export class ViewQueryParamsService {
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const timeZone = await this.getWorkspaceMemberTimezoneIfAvailable(
|
||||
workspaceId,
|
||||
currentWorkspaceMemberId,
|
||||
);
|
||||
|
||||
const recordFilters: RecordFilter[] = (view.viewFilters ?? [])
|
||||
.map((viewFilter) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
@@ -122,7 +131,7 @@ export class ViewQueryParamsService {
|
||||
fields,
|
||||
recordFilters,
|
||||
recordFilterGroups,
|
||||
filterValueDependencies: { currentWorkspaceMemberId, timeZone: 'UTC' }, // TODO: check if we need to put workspace member timezone here
|
||||
filterValueDependencies: { currentWorkspaceMemberId, timeZone },
|
||||
});
|
||||
|
||||
const orderBy: ObjectRecordOrderBy = (view.viewSorts ?? [])
|
||||
@@ -151,4 +160,29 @@ export class ViewQueryParamsService {
|
||||
viewType: view.type,
|
||||
};
|
||||
}
|
||||
|
||||
private async getWorkspaceMemberTimezoneIfAvailable(
|
||||
workspaceId: string,
|
||||
currentWorkspaceMemberId?: string,
|
||||
): Promise<string> {
|
||||
if (!isDefined(currentWorkspaceMemberId)) {
|
||||
return DEFAULT_TIMEZONE;
|
||||
}
|
||||
|
||||
try {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
);
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: currentWorkspaceMemberId },
|
||||
});
|
||||
|
||||
return workspaceMember?.timeZone ?? DEFAULT_TIMEZONE;
|
||||
} catch {
|
||||
return DEFAULT_TIMEZONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -243,6 +243,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED: false,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
|
||||
IS_MARKETPLACE_ENABLED: false,
|
||||
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED: false,
|
||||
IS_FILES_FIELD_MIGRATED: false,
|
||||
IS_DRAFT_EMAIL_ENABLED: false,
|
||||
IS_CORE_PICTURE_MIGRATED: false,
|
||||
|
||||
+5
@@ -101,6 +101,11 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
import {
|
||||
FieldMetadataType,
|
||||
@@ -52,7 +53,6 @@ import {
|
||||
import { arrayOfStringsOrVariablesSchema } from '@/utils/filter/utils/validation-schemas/arrayOfStringsOrVariablesSchema';
|
||||
import { arrayOfUuidOrVariableSchema } from '@/utils/filter/utils/validation-schemas/arrayOfUuidsOrVariablesSchema';
|
||||
import { jsonRelationFilterValueSchema } from '@/utils/filter/utils/validation-schemas/jsonRelationFilterValueSchema';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
||||
type FieldShared = {
|
||||
id: string;
|
||||
@@ -390,6 +390,43 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
|
||||
throw new Error(`Date filter is empty`);
|
||||
}
|
||||
|
||||
if (recordFilter.operand === RecordFilterOperand.IS) {
|
||||
const timeZone = filterValueDependencies.timeZone ?? 'UTC';
|
||||
|
||||
let parsedPlainDate = null;
|
||||
|
||||
try {
|
||||
parsedPlainDate = recordFilter.value.includes('T')
|
||||
? Temporal.Instant.from(recordFilter.value)
|
||||
.toZonedDateTimeISO(timeZone)
|
||||
.toPlainDate()
|
||||
: Temporal.PlainDate.from(recordFilter.value);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Cannot parse "${recordFilter.value}" for ${filterType} filter`,
|
||||
);
|
||||
}
|
||||
|
||||
const zonedDateTime = parsedPlainDate.toZonedDateTime(timeZone);
|
||||
const start = zonedDateTime.toInstant();
|
||||
const end = zonedDateTime.add({ days: 1 }).toInstant();
|
||||
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
[correspondingFieldMetadataItem.name]: {
|
||||
gte: start.toString(),
|
||||
} as DateTimeFilter,
|
||||
},
|
||||
{
|
||||
[correspondingFieldMetadataItem.name]: {
|
||||
lt: end.toString(),
|
||||
} as DateTimeFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const resolvedDateTime = Temporal.Instant.from(recordFilter.value);
|
||||
|
||||
switch (recordFilter.operand) {
|
||||
@@ -407,34 +444,6 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
|
||||
} as DateTimeFilter,
|
||||
};
|
||||
}
|
||||
case RecordFilterOperand.IS: {
|
||||
const start = resolvedDateTime
|
||||
.toZonedDateTimeISO('UTC')
|
||||
.with({
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
microsecond: 0,
|
||||
nanosecond: 0,
|
||||
})
|
||||
.toInstant();
|
||||
|
||||
const end = start.add({ minutes: 1 });
|
||||
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
[correspondingFieldMetadataItem.name]: {
|
||||
lt: end.toString(),
|
||||
} as DateTimeFilter,
|
||||
},
|
||||
{
|
||||
[correspondingFieldMetadataItem.name]: {
|
||||
gte: start.toString(),
|
||||
} as DateTimeFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user