Validate timezone input in group-by date queries (#17777)
## Summary - Validates that the timezone parameter in group-by date expressions is a recognized IANA timezone - Adds SQL string literal escaping as a defense-in-depth measure for the timezone value interpolated into SQL expressions - Moves the `IANA_TIME_ZONES` constant to `twenty-shared` so it can be reused across frontend and server packages - Adds `INVALID_TIMEZONE` error code mapped to 400 Bad Request in both GraphQL and REST API exception handlers ## Test plan - [x] Unit tests for `validateIanaTimeZone` (valid IANA zones, fixed offsets, rejects invalid strings) - [x] Unit tests for `escapeSqlStringLiteral` - [x] Integration tests for `getGroupByExpression` covering timezone validation and granularity handling Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { IANA_TIME_ZONES } from '@/localization/constants/IanaTimeZones';
|
||||
import { IANA_TIME_ZONES } from 'twenty-shared/constants';
|
||||
import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ import {
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { GroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-definition.type';
|
||||
import { GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
|
||||
import { GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { formatResultWithGroupByDimensionValues } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/format-result-with-group-by-dimension-values.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
|
||||
import { parseGroupByArgs } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/parse-group-by-args.util';
|
||||
import { GroupByWithRecordsService } from 'src/engine/api/graphql/graphql-query-runner/group-by/services/group-by-with-records.service';
|
||||
import { getGroupLimit } from 'src/engine/api/graphql/graphql-query-runner/group-by/utils/get-group-limit.util';
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ export enum CommonQueryRunnerExceptionCode {
|
||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||
TOO_COMPLEX_QUERY = 'TOO_COMPLEX_QUERY',
|
||||
MISSING_TIMEZONE_FOR_DATE_GROUP_BY = 'MISSING_TIMEZONE_FOR_DATE_GROUP_BY',
|
||||
INVALID_TIMEZONE = 'INVALID_TIMEZONE',
|
||||
}
|
||||
|
||||
export class CommonQueryRunnerException extends CustomException<CommonQueryRunnerExceptionCode> {
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectRecordGroupByDateGranularity,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type GroupByDateField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { getGroupByExpression } from 'src/engine/api/common/common-query-runners/utils/get-group-by-expression.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
const buildDateTimeFieldMetadata = (): FlatFieldMetadata => {
|
||||
return {
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
name: 'createdAt',
|
||||
} as FlatFieldMetadata;
|
||||
};
|
||||
|
||||
const buildGroupByDateField = (
|
||||
overrides: Partial<GroupByDateField> = {},
|
||||
): GroupByDateField => ({
|
||||
fieldMetadata: buildDateTimeFieldMetadata(),
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity.DAY,
|
||||
timeZone: 'America/New_York',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('getGroupByExpression', () => {
|
||||
const columnNameWithQuotes = '"company"."createdAt"';
|
||||
|
||||
describe('timezone validation', () => {
|
||||
it('should accept valid IANA timezones', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
timeZone: 'America/New_York',
|
||||
});
|
||||
|
||||
const result = getGroupByExpression({
|
||||
groupByField,
|
||||
columnNameWithQuotes,
|
||||
});
|
||||
|
||||
expect(result).toContain("'America/New_York'");
|
||||
});
|
||||
|
||||
it('should accept UTC timezone', () => {
|
||||
const groupByField = buildGroupByDateField({ timeZone: 'UTC' });
|
||||
|
||||
const result = getGroupByExpression({
|
||||
groupByField,
|
||||
columnNameWithQuotes,
|
||||
});
|
||||
|
||||
expect(result).toContain("'UTC'");
|
||||
});
|
||||
|
||||
it('should reject SQL injection in timezone field', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
timeZone: "UTC'; DROP TABLE users; --",
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
getGroupByExpression({ groupByField, columnNameWithQuotes }),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should reject UNION-based injection in timezone', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
timeZone: "UTC') UNION SELECT * FROM pg_shadow --",
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
getGroupByExpression({ groupByField, columnNameWithQuotes }),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should reject arbitrary strings as timezone', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
timeZone: 'not_a_real_timezone',
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
getGroupByExpression({ groupByField, columnNameWithQuotes }),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('missing timezone handling', () => {
|
||||
it('should throw when timezone is required but not provided', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
timeZone: undefined,
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
getGroupByExpression({ groupByField, columnNameWithQuotes }),
|
||||
).toThrow(
|
||||
'Time zone should be specified for a group by date on Day, Week, Month, Quarter or Year',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('granularity without timezone', () => {
|
||||
it('should return column directly for NONE granularity', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity.NONE,
|
||||
timeZone: undefined,
|
||||
});
|
||||
|
||||
const result = getGroupByExpression({
|
||||
groupByField,
|
||||
columnNameWithQuotes,
|
||||
});
|
||||
|
||||
expect(result).toBe(columnNameWithQuotes);
|
||||
});
|
||||
|
||||
it('should handle DAY_OF_THE_WEEK without timezone', () => {
|
||||
const groupByField = buildGroupByDateField({
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK,
|
||||
timeZone: undefined,
|
||||
});
|
||||
|
||||
const result = getGroupByExpression({
|
||||
groupByField,
|
||||
columnNameWithQuotes,
|
||||
});
|
||||
|
||||
expect(result).toContain('TMDay');
|
||||
expect(result).not.toContain('AT TIME ZONE');
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
@@ -28,6 +28,7 @@ export const commonQueryRunnerToGraphqlApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.BAD_REQUEST:
|
||||
case CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY:
|
||||
case CommonQueryRunnerExceptionCode.MISSING_TIMEZONE_FOR_DATE_GROUP_BY:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_TIMEZONE:
|
||||
throw new UserInputError(error);
|
||||
case CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT:
|
||||
throw new AuthenticationError(error);
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ export const commonQueryRunnerToRestApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.BAD_REQUEST:
|
||||
case CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY:
|
||||
case CommonQueryRunnerExceptionCode.MISSING_TIMEZONE_FOR_DATE_GROUP_BY:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_TIMEZONE:
|
||||
throw new BadRequestException(error.message);
|
||||
case CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundException('Record not found');
|
||||
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
import { type GroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-definition.type';
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { getGroupByExpression } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/get-group-by-expression.util';
|
||||
import { isGroupByDateField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
import { formatColumnNameAsAlias } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/remove-quote.util';
|
||||
import { type GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { getGroupByExpression } from 'src/engine/api/common/common-query-runners/utils/get-group-by-expression.util';
|
||||
import { isGroupByDateField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
|
||||
import { formatColumnNameAsAlias } from 'src/engine/api/common/common-query-runners/utils/remove-quote.util';
|
||||
import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util';
|
||||
|
||||
export const getGroupByDefinitions = ({
|
||||
|
||||
+21
-4
@@ -1,5 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { GROUP_BY_DATE_GRANULARITY_THAT_REQUIRE_TIME_ZONE } from 'twenty-shared/constants';
|
||||
import {
|
||||
GROUP_BY_DATE_GRANULARITY_THAT_REQUIRE_TIME_ZONE,
|
||||
IANA_TIME_ZONES,
|
||||
} from 'twenty-shared/constants';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectRecordGroupByDateGranularity,
|
||||
@@ -11,9 +14,11 @@ import {
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { isGroupByDateField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { isGroupByDateField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
|
||||
|
||||
const VALID_IANA_TIMEZONES = new Set(IANA_TIME_ZONES);
|
||||
|
||||
export const getGroupByExpression = ({
|
||||
groupByField,
|
||||
@@ -49,6 +54,18 @@ export const getGroupByExpression = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
shouldUseTimeZone &&
|
||||
!timeZoneIsNotProvided &&
|
||||
!VALID_IANA_TIMEZONES.has(groupByField.timeZone!)
|
||||
) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid timezone: ${groupByField.timeZone}`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_TIMEZONE,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
const timeZoneAsDateTruncParameter = shouldUseTimeZone
|
||||
? `, '${groupByField.timeZone}'`
|
||||
: '';
|
||||
+4
-4
@@ -4,10 +4,10 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { getGroupByExpression } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/get-group-by-expression.util';
|
||||
import { isGroupByDateField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { getGroupByExpression } from 'src/engine/api/common/common-query-runners/utils/get-group-by-expression.util';
|
||||
import { isGroupByDateField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-date-field.util';
|
||||
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
|
||||
|
||||
const DAYS_OF_WEEK = [
|
||||
'Monday',
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type GroupByDateField,
|
||||
type GroupByField,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
} from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
|
||||
export const isGroupByDateField = (
|
||||
groupByField: GroupByField,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type GroupByField,
|
||||
type GroupByRelationField,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
} from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
|
||||
export const isGroupByRelationField = (
|
||||
groupByField: GroupByField,
|
||||
+2
-2
@@ -20,8 +20,8 @@ import {
|
||||
type GroupByDateField,
|
||||
type GroupByField,
|
||||
type GroupByRegularField,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { getGroupByOrderExpression } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/get-group-by-order-expression.util';
|
||||
} from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { getGroupByOrderExpression } from 'src/engine/api/common/common-query-runners/utils/get-group-by-order-expression.util';
|
||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import {
|
||||
type AggregationField,
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type GroupByField,
|
||||
type GroupByRelationField,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
} from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import {
|
||||
GraphqlQuerySelectedFieldsParser,
|
||||
type GraphqlQuerySelectedFieldsResult,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
|
||||
import { type GroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-definition.type';
|
||||
import { type GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type GroupByResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { isGroupByDateFieldDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field-definition.util';
|
||||
import { parseGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/parse-group-by-relation-field.util';
|
||||
import { validateSingleKeyForGroupByOrThrow } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/validate-single-key-for-group-by-or-throw.util';
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { type CompositeFieldGroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/composite-field-group-by-definition.type';
|
||||
import { type DateFieldGroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/date-field-group-by-definition.type';
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { type GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
|
||||
import { isGroupByDateFieldDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field-definition.util';
|
||||
import { validateSingleKeyForGroupByOrThrow } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/validate-single-key-for-group-by-or-throw.util';
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import { type CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
|
||||
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { type GroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-definition.type';
|
||||
import { type GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
|
||||
import { formatResultWithGroupByDimensionValues } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/format-result-with-group-by-dimension-values.util';
|
||||
import { getGroupLimit } from 'src/engine/api/graphql/graphql-query-runner/group-by/utils/get-group-limit.util';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
|
||||
+2
-4
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* Standard IANA time zones.
|
||||
* @see https://www.iana.org/time-zones
|
||||
*/
|
||||
// Standard IANA time zones
|
||||
// https://www.iana.org/time-zones
|
||||
export const IANA_TIME_ZONES = [
|
||||
'Africa/Abidjan',
|
||||
'Africa/Accra',
|
||||
@@ -26,6 +26,7 @@ export { MAX_OPTIONS_TO_DISPLAY } from './FieldMetadataMaxOptionsToDisplay';
|
||||
export { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from './FieldRestrictedAdditionalPermissionsRequired';
|
||||
export { FILES_FIELD_MAX_NUMBER_OF_VALUES } from './FilesFieldMaxNumberOfValues';
|
||||
export { GROUP_BY_DATE_GRANULARITY_THAT_REQUIRE_TIME_ZONE } from './GroupByDateGranularityThatRequireTimeZone';
|
||||
export { IANA_TIME_ZONES } from './IanaTimeZones';
|
||||
export { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from './LabelIdentifierFieldMetadataTypes';
|
||||
export { MULTI_ITEM_FIELD_DEFAULT_MAX_VALUES } from './MultiItemFieldDefaultMaxValues';
|
||||
export { MULTI_ITEM_FIELD_MIN_MAX_VALUES } from './MultiItemFieldMinMaxValues';
|
||||
|
||||
Reference in New Issue
Block a user