diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts index c5bc7fc9c..8550a3963 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts @@ -2,11 +2,15 @@ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event'; @Injectable() export class TelemetryListener { - constructor(private readonly analyticsService: AnalyticsService) {} + constructor( + private readonly analyticsService: AnalyticsService, + private readonly environmentService: EnvironmentService, + ) {} @OnEvent('*.created') async handleAllCreate(payload: ObjectRecordCreateEvent) { @@ -21,7 +25,7 @@ export class TelemetryListener { payload.workspaceId, '', // voluntarely not retrieving this '', // to avoid slowing down - '', + this.environmentService.get('SERVER_URL'), ); } @@ -38,7 +42,7 @@ export class TelemetryListener { payload.workspaceId, '', '', - '', + this.environmentService.get('SERVER_URL'), ); } } diff --git a/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts index 8be1e4e6d..fe2dfacae 100644 --- a/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts +++ b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts @@ -22,28 +22,28 @@ export class RestApiCoreController { async handleApiGet(@Req() request: Request, @Res() res: Response) { const result = await this.restApiCoreService.get(request); - res.status(200).send(cleanGraphQLResponse(result.data)); + res.status(200).send(cleanGraphQLResponse(result.data.data)); } @Delete() async handleApiDelete(@Req() request: Request, @Res() res: Response) { const result = await this.restApiCoreService.delete(request); - res.status(200).send(cleanGraphQLResponse(result.data)); + res.status(200).send(cleanGraphQLResponse(result.data.data)); } @Post() async handleApiPost(@Req() request: Request, @Res() res: Response) { const result = await this.restApiCoreService.createOne(request); - res.status(201).send(cleanGraphQLResponse(result.data)); + res.status(201).send(cleanGraphQLResponse(result.data.data)); } @Patch() async handleApiPatch(@Req() request: Request, @Res() res: Response) { const result = await this.restApiCoreService.update(request); - res.status(200).send(cleanGraphQLResponse(result.data)); + res.status(200).send(cleanGraphQLResponse(result.data.data)); } // This endpoint is not documented in the OpenAPI schema. @@ -53,6 +53,6 @@ export class RestApiCoreController { async handleApiPut(@Req() request: Request, @Res() res: Response) { const result = await this.restApiCoreService.update(request); - res.status(200).send(cleanGraphQLResponse(result.data)); + res.status(200).send(cleanGraphQLResponse(result.data.data)); } } diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts index d6bbd273f..d583d2dfd 100644 --- a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts @@ -7,11 +7,12 @@ import { DeleteVariablesFactory } from 'src/engine/api/rest/rest-api-core-query- import { CreateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory'; import { UpdateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory'; import { GetVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory'; -import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; import { LimitInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory'; import { OrderByInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; import { FilterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory'; import { CreateManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory'; +import { StartingAfterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory'; +import { EndingBeforeInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory'; export const coreQueryBuilderFactories = [ DeleteQueryFactory, @@ -24,7 +25,8 @@ export const coreQueryBuilderFactories = [ CreateVariablesFactory, UpdateVariablesFactory, GetVariablesFactory, - LastCursorInputFactory, + StartingAfterInputFactory, + EndingBeforeInputFactory, LimitInputFactory, OrderByInputFactory, FilterInputFactory, diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts index 4d7fd2c18..bd6bd1e45 100644 --- a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts @@ -15,11 +15,12 @@ export class FindManyQueryFactory { query FindMany${capitalize(objectNamePlural)}( $filter: ${objectNameSingular}FilterInput, $orderBy: ${objectNameSingular}OrderByInput, - $lastCursor: String, + $startingAfter: String, + $endingBefore: String, $limit: Int = 60 ) { ${objectNamePlural}( - filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor + filter: $filter, orderBy: $orderBy, first: $limit, after: $startingAfter, before: $endingBefore ) { edges { node { diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts index c838ce14e..8703e67f7 100644 --- a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts @@ -2,16 +2,18 @@ import { Injectable } from '@nestjs/common'; import { Request } from 'express'; -import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; import { LimitInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory'; import { OrderByInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; import { FilterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory'; import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; +import { EndingBeforeInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory'; +import { StartingAfterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory'; @Injectable() export class GetVariablesFactory { constructor( - private readonly lastCursorInputFactory: LastCursorInputFactory, + private readonly startingAfterInputFactory: StartingAfterInputFactory, + private readonly endingBeforeInputFactory: EndingBeforeInputFactory, private readonly limitInputFactory: LimitInputFactory, private readonly orderByInputFactory: OrderByInputFactory, private readonly filterInputFactory: FilterInputFactory, @@ -30,7 +32,8 @@ export class GetVariablesFactory { filter: this.filterInputFactory.create(request, objectMetadata), orderBy: this.orderByInputFactory.create(request, objectMetadata), limit: this.limitInputFactory.create(request), - lastCursor: this.lastCursorInputFactory.create(request), + startingAfter: this.startingAfterInputFactory.create(request), + endingBefore: this.endingBeforeInputFactory.create(request), }; } } diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/ending-before-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/ending-before-input.factory.spec.ts new file mode 100644 index 000000000..2de86ff7f --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/ending-before-input.factory.spec.ts @@ -0,0 +1,33 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { EndingBeforeInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory'; + +describe('EndingBeforeInputFactory', () => { + let service: EndingBeforeInputFactory; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [EndingBeforeInputFactory], + }).compile(); + + service = module.get(EndingBeforeInputFactory); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('create', () => { + it('should return default if ending_before missing', () => { + const request: any = { query: {} }; + + expect(service.create(request)).toEqual(undefined); + }); + + it('should return ending_before', () => { + const request: any = { query: { ending_before: 'uuid' } }; + + expect(service.create(request)).toEqual('uuid'); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts deleted file mode 100644 index e979584fb..000000000 --- a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; - -describe('LastCursorInputFactory', () => { - let service: LastCursorInputFactory; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [LastCursorInputFactory], - }).compile(); - - service = module.get(LastCursorInputFactory); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('create', () => { - it('should return default if last_cursor missing', () => { - const request: any = { query: {} }; - - expect(service.create(request)).toEqual(undefined); - }); - - it('should return last_cursor', () => { - const request: any = { query: { last_cursor: 'uuid' } }; - - expect(service.create(request)).toEqual('uuid'); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/starting-before-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/starting-before-input.factory.spec.ts new file mode 100644 index 000000000..484cd8da6 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/starting-before-input.factory.spec.ts @@ -0,0 +1,33 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { StartingAfterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory'; + +describe('StartingAfterInputFactory', () => { + let service: StartingAfterInputFactory; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [StartingAfterInputFactory], + }).compile(); + + service = module.get(StartingAfterInputFactory); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('create', () => { + it('should return default if starting_after missing', () => { + const request: any = { query: {} }; + + expect(service.create(request)).toEqual(undefined); + }); + + it('should return starting_after', () => { + const request: any = { query: { starting_after: 'uuid' } }; + + expect(service.create(request)).toEqual('uuid'); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory.ts similarity index 72% rename from packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory.ts index 57ffae171..9cde5e1cf 100644 --- a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/ending-before-input.factory.ts @@ -3,9 +3,9 @@ import { Injectable } from '@nestjs/common'; import { Request } from 'express'; @Injectable() -export class LastCursorInputFactory { +export class EndingBeforeInputFactory { create(request: Request): string | undefined { - const cursorQuery = request.query.last_cursor; + const cursorQuery = request.query.ending_before; if (typeof cursorQuery !== 'string') { return undefined; diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory.ts new file mode 100644 index 000000000..601c663c4 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/starting-after-input.factory.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; + +import { Request } from 'express'; + +@Injectable() +export class StartingAfterInputFactory { + create(request: Request): string | undefined { + const cursorQuery = request.query.starting_after; + + if (typeof cursorQuery !== 'string') { + return undefined; + } + + return cursorQuery; + } +} diff --git a/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts b/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts index 2554be3e4..6907aed8d 100644 --- a/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts +++ b/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts @@ -4,6 +4,7 @@ export type QueryVariables = { filter?: object; orderBy?: object; limit?: number; - lastCursor?: string; + startingAfter?: string; + endingBefore?: string; input?: object; }; diff --git a/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts index 2dd422282..0bba89b4a 100644 --- a/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts @@ -12,7 +12,9 @@ describe('cleanGraphQLResponse', () => { }, }; const expectedResult = { - companies: [{ id: 'id', createdAt: '2023-01-01' }], + data: { + companies: [{ id: 'id', createdAt: '2023-01-01' }], + }, }; expect(cleanGraphQLResponse(data)).toEqual(expectedResult); @@ -20,6 +22,13 @@ describe('cleanGraphQLResponse', () => { it('should remove nested edges/node from results', () => { const data = { companies: { + totalCount: 14, + pageInfo: { + hasNextPage: true, + startCursor: + 'WyIwMDliYjNkYy1hNGEyLTRiNWUtYTZmYi1iMTFiMmFlMGI1MmIiXQ==', + endCursor: 'WyIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==', + }, edges: [ { node: { @@ -34,20 +43,33 @@ describe('cleanGraphQLResponse', () => { }, }; const expectedResult = { - companies: [ - { - id: 'id', - createdAt: '2023-01-01', - people: [{ id: 'id1' }, { id: 'id2' }], - }, - ], + data: { + companies: [ + { + id: 'id', + createdAt: '2023-01-01', + people: [{ id: 'id1' }, { id: 'id2' }], + }, + ], + }, + totalCount: 14, + pageInfo: { + hasNextPage: true, + startCursor: 'WyIwMDliYjNkYy1hNGEyLTRiNWUtYTZmYi1iMTFiMmFlMGI1MmIiXQ==', + endCursor: 'WyIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==', + }, }; expect(cleanGraphQLResponse(data)).toEqual(expectedResult); }); it('should not format when no list returned', () => { const data = { company: { id: 'id' } }; + const expectedResult = { + data: { + company: { id: 'id' }, + }, + }; - expect(cleanGraphQLResponse(data)).toEqual(data); + expect(cleanGraphQLResponse(data)).toEqual(expectedResult); }); }); diff --git a/packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts b/packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts index 76a2ec5fe..a1f39615e 100644 --- a/packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts @@ -1,16 +1,39 @@ -// https://gist.github.com/ManUtopiK/469aec75b655d6a4d912aeb3b75af3c9 export const cleanGraphQLResponse = (input: any) => { if (!input) return null; - const output = {}; + const output = { data: {} }; // Initialize the output with a data key at the top level + const isObject = (obj: any) => { return obj !== null && typeof obj === 'object' && !Array.isArray(obj); }; + const cleanObject = (obj: any) => { + const cleanedObj = {}; + + Object.keys(obj).forEach((key) => { + if (isObject(obj[key])) { + if (obj[key].edges) { + // Handle edges by mapping over them and applying cleanObject to each node + cleanedObj[key] = obj[key].edges.map((edge) => + cleanObject(edge.node), + ); + } else { + // Recursively clean nested objects + cleanedObj[key] = cleanObject(obj[key]); + } + } else { + // Directly assign non-object properties + cleanedObj[key] = obj[key]; + } + }); + + return cleanedObj; + }; + Object.keys(input).forEach((key) => { - if (input[key] && input[key].edges) { - output[key] = input[key].edges.map((edge) => - cleanGraphQLResponse(edge.node), - ); + if (isObject(input[key]) && input[key].edges) { + // Handle collections with edges, ensuring data is placed under the data key + output.data[key] = input[key].edges.map((edge) => cleanObject(edge.node)); + // Move pageInfo and totalCount to the top level if (input[key].pageInfo) { output['pageInfo'] = input[key].pageInfo; } @@ -18,9 +41,11 @@ export const cleanGraphQLResponse = (input: any) => { output['totalCount'] = input[key].totalCount; } } else if (isObject(input[key])) { - output[key] = cleanGraphQLResponse(input[key]); - } else if (key !== '__typename') { - output[key] = input[key]; + // Recursively clean and assign nested objects under the data key + output.data[key] = cleanObject(input[key]); + } else { + // Assign all other properties directly under the data key + output.data[key] = input[key]; } }); diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts index d0c229f27..7fcba0c9f 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts @@ -8,6 +8,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { User } from 'src/engine/core-modules/user/user.entity'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { AnalyticsService } from './analytics.service'; import { Analytics } from './analytics.entity'; @@ -17,7 +18,10 @@ import { CreateAnalyticsInput } from './dtos/create-analytics.input'; @UseGuards(OptionalJwtAuthGuard) @Resolver(() => Analytics) export class AnalyticsResolver { - constructor(private readonly analyticsService: AnalyticsService) {} + constructor( + private readonly analyticsService: AnalyticsService, + private readonly environmentService: EnvironmentService, + ) {} @Mutation(() => Analytics) track( @@ -32,7 +36,7 @@ export class AnalyticsResolver { workspace?.id, workspace?.displayName, workspace?.domainName, - request.hostname, + this.environmentService.get('SERVER_URL') ?? request.hostname, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index 361b3bede..3fd99913c 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -2,11 +2,12 @@ import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder import { computeDepthParameters, + computeEndingBeforeParameters, computeFilterParameters, computeIdPathParameter, - computeLastCursorParameters, computeLimitParameters, computeOrderByParameters, + computeStartingAfterParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; import { FilterComparators } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; @@ -106,13 +107,27 @@ describe('computeParameters', () => { }); }); }); - describe('computeLastCursor', () => { - it('should compute last cursor', () => { - expect(computeLastCursorParameters()).toEqual({ - name: 'last_cursor', + describe('computeStartingAfter', () => { + it('should compute starting after', () => { + expect(computeStartingAfterParameters()).toEqual({ + name: 'starting_after', in: 'query', description: - 'Returns objects starting from a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', + 'Returns objects starting after a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', + required: false, + schema: { + type: 'string', + }, + }); + }); + }); + describe('computeEndingBefore', () => { + it('should compute ending_before', () => { + expect(computeEndingBeforeParameters()).toEqual({ + name: 'ending_before', + in: 'query', + description: + 'Returns objects ending before a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', required: false, schema: { type: 'string', diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 37ebb6d13..62255806f 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -5,11 +5,12 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi import { capitalize } from 'src/utils/capitalize'; import { computeDepthParameters, + computeEndingBeforeParameters, computeFilterParameters, computeIdPathParameter, - computeLastCursorParameters, computeLimitParameters, computeOrderByParameters, + computeStartingAfterParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; @@ -223,7 +224,8 @@ export const computeParameterComponents = (): Record< > => { return { idPath: computeIdPathParameter(), - lastCursor: computeLastCursorParameters(), + startingAfter: computeStartingAfterParameters(), + endingBefore: computeEndingBeforeParameters(), filter: computeFilterParameters(), depth: computeDepthParameters(), orderBy: computeOrderByParameters(), diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index 4ce72b089..b07c297e0 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -95,18 +95,33 @@ export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => { }; }; -export const computeLastCursorParameters = (): OpenAPIV3_1.ParameterObject => { - return { - name: 'last_cursor', - in: 'query', - description: - 'Returns objects starting from a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', - required: false, - schema: { - type: 'string', - }, +export const computeStartingAfterParameters = + (): OpenAPIV3_1.ParameterObject => { + return { + name: 'starting_after', + in: 'query', + description: + 'Returns objects starting after a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', + required: false, + schema: { + type: 'string', + }, + }; + }; + +export const computeEndingBeforeParameters = + (): OpenAPIV3_1.ParameterObject => { + return { + name: 'ending_before', + in: 'query', + description: + 'Returns objects ending before a specific cursor. You can find cursors in **startCursor** and **endCursor** in **pageInfo** in response data', + required: false, + schema: { + type: 'string', + }, + }; }; -}; export const computeIdPathParameter = (): OpenAPIV3_1.ParameterObject => { return { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts index cb15a47af..37b1e217a 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts @@ -42,14 +42,15 @@ export const computeManyResultPath = ( get: { tags: [item.namePlural], summary: `Find Many ${item.namePlural}`, - description: `**order_by**, **filter**, **limit**, **depth** or **last_cursor** can be provided to request your **${item.namePlural}**`, + description: `**order_by**, **filter**, **limit**, **depth**, **starting_after** or **ending_before** can be provided to request your **${item.namePlural}**`, operationId: `findMany${capitalize(item.namePlural)}`, parameters: [ { $ref: '#/components/parameters/orderBy' }, { $ref: '#/components/parameters/filter' }, { $ref: '#/components/parameters/limit' }, { $ref: '#/components/parameters/depth' }, - { $ref: '#/components/parameters/lastCursor' }, + { $ref: '#/components/parameters/startingAfter' }, + { $ref: '#/components/parameters/endingBefore' }, ], responses: { '200': getFindManyResponse200(item), diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts index d72bd07cf..7793a507f 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts @@ -22,19 +22,19 @@ export const getFindManyResponse200 = ( )} with Relations`, }, }, - pageInfo: { - type: 'object', - properties: { - hasNextPage: { type: 'boolean' }, - startCursor: { type: 'string' }, - endCursor: { type: 'string' }, - }, - }, - totalCount: { - type: 'integer', - }, }, }, + pageInfo: { + type: 'object', + properties: { + hasNextPage: { type: 'boolean' }, + startCursor: { type: 'string' }, + endCursor: { type: 'string' }, + }, + }, + totalCount: { + type: 'integer', + }, }, example: { data: { @@ -44,6 +44,12 @@ export const getFindManyResponse200 = ( '...', ], }, + pageInfo: { + hasNextPage: true, + startCursor: '56f411fb-0900-4ffb-b942-d7e8d6709eff', + endCursor: '93adf3c6-6cf7-4a86-adcd-75f77857ba67', + }, + totalCount: 132, }, }, }, diff --git a/packages/twenty-server/src/modules/messaging/common/services/messaging-telemetry.service.ts b/packages/twenty-server/src/modules/messaging/common/services/messaging-telemetry.service.ts index 4a70a7330..ae9cec0fa 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/messaging-telemetry.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/messaging-telemetry.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; type MessagingTelemetryTrackInput = { eventName: string; @@ -13,7 +14,10 @@ type MessagingTelemetryTrackInput = { @Injectable() export class MessagingTelemetryService { - constructor(private readonly analyticsService: AnalyticsService) {} + constructor( + private readonly analyticsService: AnalyticsService, + private readonly environmentService: EnvironmentService, + ) {} public async track({ eventName, @@ -39,7 +43,7 @@ export class MessagingTelemetryService { workspaceId, '', // voluntarely not retrieving this '', // to avoid slowing down - '', + this.environmentService.get('SERVER_URL'), ); } } diff --git a/packages/twenty-website/src/app/_components/playground/graphql-playground.tsx b/packages/twenty-website/src/app/_components/playground/graphql-playground.tsx index ad6a290c4..3f2a3e12e 100644 --- a/packages/twenty-website/src/app/_components/playground/graphql-playground.tsx +++ b/packages/twenty-website/src/app/_components/playground/graphql-playground.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; +import styled from '@emotion/styled'; import { explorerPlugin } from '@graphiql/plugin-explorer'; -import { Theme, useTheme } from '@graphiql/react'; import { createGraphiQLFetcher } from '@graphiql/toolkit'; import { GraphiQL } from 'graphiql'; @@ -9,6 +9,13 @@ import { SubDoc } from '@/app/_components/playground/token-form'; import Playground from './playground'; +import 'graphiql/graphiql.css'; +import '@graphiql/plugin-explorer/dist/style.css'; + +const StyledContainer = styled.div` + height: 100%; +`; + const SubDocToPath = { core: 'graphql', metadata: 'metadata', @@ -28,37 +35,19 @@ const GraphQlComponent = ({ token, baseUrl, path }: any) => { } return ( -
+ -
+ ); }; const GraphQlPlayground = ({ subDoc }: { subDoc: SubDoc }) => { const [token, setToken] = useState(); const [baseUrl, setBaseUrl] = useState(); - const { setTheme } = useTheme(); - - useEffect(() => { - window.localStorage.setItem( - 'graphiql:theme', - window.localStorage.getItem('theme') || 'light', - ); - - const handleThemeChange = (ev: any) => { - if (ev.key === 'theme') { - setTheme(ev.newValue as Theme); - } - }; - - window.addEventListener('storage', handleThemeChange); - - return () => window.removeEventListener('storage', handleThemeChange); - }, []); const children = (