groupBy fix + typeMapper fix (#15433)

groupBy fix: when a group's dimension value is NULL, we need to adapt
the raw sql (stage: NULL -> stage IS NULL)

typeMapper fix: a graphql type should be made non-nullable if was
indicated so + does not have a default value. our check on not having a
default value was limited to having a null defaultValue instead of
having a null or undefined defaultValue. This is a breaking change, but
all the queries that were providing a null value for these args were not
functioning anyway, and luckily in the FE we declared all queries adding
a `!` already.
This commit is contained in:
Marie
2025-10-30 10:14:56 +00:00
committed by GitHub
parent 19ea9fff97
commit 1ad8c05fbc
8 changed files with 18 additions and 13 deletions
@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import isEmpty from 'lodash.isempty';
import { ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectLiteral } from 'typeorm';
import { ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
@@ -221,8 +221,12 @@ export class GroupByWithRecordsService {
const conditions = groupByDefinitions
.map((def, defIndex) => {
const paramName = `groupValue_${groupIndex}_${defIndex}`;
const paramValue = group[def.alias];
queryBuilder.setParameter(paramName, group[def.alias]);
if (!isDefined(paramValue)) {
return `${def.expression} IS NULL`;
}
queryBuilder.setParameter(paramName, paramValue);
return `${def.expression} = :${paramName}`;
})
@@ -15,6 +15,7 @@ import {
} from 'graphql';
import GraphQLJSON from 'graphql-type-json';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
import {
@@ -204,7 +205,7 @@ export class TypeMapperService {
);
}
if (options.nullable === false && options.defaultValue === null) {
if (options.nullable === false && !isDefined(options.defaultValue)) {
graphqlType = new GraphQLNonNull(graphqlType) as unknown as T;
}
@@ -170,7 +170,7 @@ describe('basic group-by with records', () => {
const response = await makeGraphqlAPIRequest({
query: gql`
query OpportunitiesGroupBy(
$groupBy: [OpportunityGroupByInput!]
$groupBy: [OpportunityGroupByInput!]!
$filter: OpportunityFilterInput
) {
opportunitiesGroupBy(groupBy: $groupBy, filter: $filter) {
@@ -293,7 +293,7 @@ describe('basic group-by with records', () => {
const response = await makeGraphqlAPIRequest({
query: gql`
query OpportunitiesGroupBy(
$groupBy: [OpportunityGroupByInput!]
$groupBy: [OpportunityGroupByInput!]!
$filter: OpportunityFilterInput
) {
opportunitiesGroupBy(groupBy: $groupBy, filter: $filter) {
@@ -374,7 +374,7 @@ describe('basic group-by with records', () => {
const response = await makeGraphqlAPIRequest({
query: gql`
query CompaniesGroupBy(
$groupBy: [CompanyGroupByInput!]
$groupBy: [CompanyGroupByInput!]!
$filter: CompanyFilterInput
) {
companiesGroupBy(groupBy: $groupBy, filter: $filter) {
@@ -464,7 +464,7 @@ describe('basic group-by with records', () => {
return {
query: gql`
query OpportunitiesGroupBy(
$groupBy: [OpportunityGroupByInput!]
$groupBy: [OpportunityGroupByInput!]!
$filter: OpportunityFilterInput
$orderByForRecords: [OpportunityOrderByInput!]
) {
@@ -8,7 +8,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
const createRecordsQuery = gql`
mutation CreateRecords(
$data: [TestRecordObjectCreateInput!]
$data: [TestRecordObjectCreateInput!]!
$upsert: Boolean
) {
createTestRecordObjects(data: $data, upsert: $upsert) {
@@ -16,7 +16,7 @@ export const deleteManyOperationFactory = ({
}: DeleteManyOperationFactoryParams) => ({
query: gql`
mutation Delete${capitalize(objectMetadataPluralName)}(
$filter: ${capitalize(objectMetadataSingularName)}FilterInput
$filter: ${capitalize(objectMetadataSingularName)}FilterInput!
) {
delete${capitalize(objectMetadataPluralName)}(filter: $filter) {
${gqlFields}
@@ -16,7 +16,7 @@ export const destroyManyOperationFactory = ({
}: DestroyManyOperationFactoryParams) => ({
query: gql`
mutation Destroy${capitalize(objectMetadataPluralName)}(
$filter: ${capitalize(objectMetadataSingularName)}FilterInput
$filter: ${capitalize(objectMetadataSingularName)}FilterInput!
) {
destroy${capitalize(objectMetadataPluralName)}(filter: $filter) {
${gqlFields}
@@ -18,8 +18,8 @@ export const updateManyOperationFactory = ({
}: UpdateManyOperationFactoryParams) => ({
query: gql`
mutation Update${capitalize(objectMetadataPluralName)}(
$data: ${capitalize(objectMetadataSingularName)}UpdateInput
$filter: ${capitalize(objectMetadataSingularName)}FilterInput
$data: ${capitalize(objectMetadataSingularName)}UpdateInput!
$filter: ${capitalize(objectMetadataSingularName)}FilterInput!
) {
update${capitalize(objectMetadataPluralName)}(data: $data, filter: $filter) {
${gqlFields}
@@ -15,7 +15,7 @@ export const updateOneOperationFactory = ({
recordId,
}: UpdateOneOperationFactoryParams) => ({
query: gql`
mutation Update${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID, $data: ${capitalize(objectMetadataSingularName)}UpdateInput) {
mutation Update${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID!, $data: ${capitalize(objectMetadataSingularName)}UpdateInput!) {
update${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id, data: $data) {
${gqlFields}
}