4485 create a custom resolver for calendar events (#4568)

* create timeline calendar event resolver

* working on getCalendarEventsFromPersonIds

* add count query

* add calendarEventVisibility and add typing

* update calendarEvent dto

* modify calendarEvent dto

* compute calendar event visibility

* fix types

* add FieldMetadata in timeline calendar dtos and create queries and fragments

* remove fieldMatadata

* fix naming

* update resolver

* add getCalendarEventsFromCompanyId

* fix queries

* refactor queries

* fix visibility

* fix calendar event attendees bug

* visibility is working

* remove @IDField

* update gql queries

* update dto

* add error

* add enum

* throw http exception

* modify error

* Refactor calendar event visibility check

* use enum
This commit is contained in:
bosiraphael
2024-03-19 18:34:00 +01:00
committed by GitHub
parent e579554d47
commit 4ab426c52a
15 changed files with 785 additions and 5 deletions

View File

@@ -61,7 +61,7 @@ export type AuthTokens = {
export type Billing = {
__typename?: 'Billing';
billingFreeTrialDurationInDays?: Maybe<Scalars['Float']>;
billingUrl: Scalars['String'];
billingUrl?: Maybe<Scalars['String']>;
isBillingEnabled: Scalars['Boolean'];
};
@@ -417,6 +417,8 @@ export type Query = {
currentWorkspace: Workspace;
findWorkspaceFromInviteHash: Workspace;
getProductPrices: ProductPricesEntity;
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
object: Object;
@@ -450,6 +452,20 @@ export type QueryGetProductPricesArgs = {
};
export type QueryGetTimelineCalendarEventsFromCompanyIdArgs = {
companyId: Scalars['ID'];
page: Scalars['Int'];
pageSize: Scalars['Int'];
};
export type QueryGetTimelineCalendarEventsFromPersonIdArgs = {
page: Scalars['Int'];
pageSize: Scalars['Int'];
personId: Scalars['ID'];
};
export type QueryGetTimelineThreadsFromCompanyIdArgs = {
companyId: Scalars['ID'];
page: Scalars['Int'];
@@ -492,6 +508,23 @@ export type RelationConnection = {
pageInfo: PageInfo;
};
export type RelationDefinition = {
__typename?: 'RelationDefinition';
direction: RelationDefinitionType;
sourceFieldMetadata: Field;
sourceObjectMetadata: Object;
targetFieldMetadata: Field;
targetObjectMetadata: Object;
};
/** Relation definition type */
export enum RelationDefinitionType {
ManyToMany = 'MANY_TO_MANY',
ManyToOne = 'MANY_TO_ONE',
OneToMany = 'ONE_TO_MANY',
OneToOne = 'ONE_TO_ONE'
}
export type RelationDeleteResponse = {
__typename?: 'RelationDeleteResponse';
createdAt?: Maybe<Scalars['DateTime']>;
@@ -545,6 +578,45 @@ export type Telemetry = {
enabled: Scalars['Boolean'];
};
export type TimelineCalendarEvent = {
__typename?: 'TimelineCalendarEvent';
attendees: Array<TimelineCalendarEventAttendee>;
conferenceSolution: Scalars['String'];
conferenceUri: Scalars['String'];
description: Scalars['String'];
endsAt: Scalars['DateTime'];
id: Scalars['ID'];
isCanceled: Scalars['Boolean'];
isFullDay: Scalars['Boolean'];
location: Scalars['String'];
startsAt: Scalars['DateTime'];
title: Scalars['String'];
visibility: TimelineCalendarEventVisibility;
};
export type TimelineCalendarEventAttendee = {
__typename?: 'TimelineCalendarEventAttendee';
avatarUrl: Scalars['String'];
displayName: Scalars['String'];
firstName: Scalars['String'];
handle: Scalars['String'];
lastName: Scalars['String'];
personId?: Maybe<Scalars['ID']>;
workspaceMemberId?: Maybe<Scalars['ID']>;
};
/** Visibility of the calendar event */
export enum TimelineCalendarEventVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}
export type TimelineCalendarEventsWithTotal = {
__typename?: 'TimelineCalendarEventsWithTotal';
timelineCalendarEvents: Array<TimelineCalendarEvent>;
totalNumberOfCalendarEvents: Scalars['Int'];
};
export type TimelineThread = {
__typename?: 'TimelineThread';
firstParticipant: TimelineThreadParticipant;
@@ -716,6 +788,7 @@ export type Field = {
label: Scalars['String'];
name: Scalars['String'];
options?: Maybe<Scalars['JSON']>;
relationDefinition?: Maybe<RelationDefinition>;
toRelationMetadata?: Maybe<Relation>;
type: FieldMetadataType;
updatedAt: Scalars['DateTime'];
@@ -794,6 +867,30 @@ export type RelationEdge = {
node: Relation;
};
export type AttendeeFragmentFragment = { __typename?: 'TimelineCalendarEventAttendee', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
export type CalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: string, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, attendees: Array<{ __typename?: 'TimelineCalendarEventAttendee', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
export type TimelineCalendarEventsWithTotalFragmentFragment = { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: string, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, attendees: Array<{ __typename?: 'TimelineCalendarEventAttendee', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
export type GetTimelineCalendarEventsFromCompanyIdQueryVariables = Exact<{
companyId: Scalars['ID'];
page: Scalars['Int'];
pageSize: Scalars['Int'];
}>;
export type GetTimelineCalendarEventsFromCompanyIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromCompanyId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: string, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, attendees: Array<{ __typename?: 'TimelineCalendarEventAttendee', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type GetTimelineCalendarEventsFromPersonIdQueryVariables = Exact<{
personId: Scalars['ID'];
page: Scalars['Int'];
pageSize: Scalars['Int'];
}>;
export type GetTimelineCalendarEventsFromPersonIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromPersonId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: string, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, attendees: Array<{ __typename?: 'TimelineCalendarEventAttendee', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: string, read: boolean, visibility: string, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
@@ -937,7 +1034,7 @@ export type GetProductPricesQuery = { __typename?: 'Query', getProductPrices: {
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl: string, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null } } };
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null } } };
export type UploadFileMutationVariables = Exact<{
file: Scalars['Upload'];
@@ -1007,6 +1104,39 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } };
export const AttendeeFragmentFragmentDoc = gql`
fragment AttendeeFragment on TimelineCalendarEventAttendee {
personId
workspaceMemberId
firstName
lastName
displayName
avatarUrl
handle
}
`;
export const CalendarEventFragmentFragmentDoc = gql`
fragment CalendarEventFragment on TimelineCalendarEvent {
id
title
description
location
startsAt
endsAt
isFullDay
attendees {
...AttendeeFragment
}
}
${AttendeeFragmentFragmentDoc}`;
export const TimelineCalendarEventsWithTotalFragmentFragmentDoc = gql`
fragment TimelineCalendarEventsWithTotalFragment on TimelineCalendarEventsWithTotal {
totalNumberOfCalendarEvents
timelineCalendarEvents {
...CalendarEventFragment
}
}
${CalendarEventFragmentFragmentDoc}`;
export const ParticipantFragmentFragmentDoc = gql`
fragment ParticipantFragment on TimelineThreadParticipant {
personId
@@ -1103,6 +1233,88 @@ export const UserQueryFragmentFragmentDoc = gql`
}
}
`;
export const GetTimelineCalendarEventsFromCompanyIdDocument = gql`
query GetTimelineCalendarEventsFromCompanyId($companyId: ID!, $page: Int!, $pageSize: Int!) {
getTimelineCalendarEventsFromCompanyId(
companyId: $companyId
page: $page
pageSize: $pageSize
) {
...TimelineCalendarEventsWithTotalFragment
}
}
${TimelineCalendarEventsWithTotalFragmentFragmentDoc}`;
/**
* __useGetTimelineCalendarEventsFromCompanyIdQuery__
*
* To run a query within a React component, call `useGetTimelineCalendarEventsFromCompanyIdQuery` and pass it any options that fit your needs.
* When your component renders, `useGetTimelineCalendarEventsFromCompanyIdQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetTimelineCalendarEventsFromCompanyIdQuery({
* variables: {
* companyId: // value for 'companyId'
* page: // value for 'page'
* pageSize: // value for 'pageSize'
* },
* });
*/
export function useGetTimelineCalendarEventsFromCompanyIdQuery(baseOptions: Apollo.QueryHookOptions<GetTimelineCalendarEventsFromCompanyIdQuery, GetTimelineCalendarEventsFromCompanyIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetTimelineCalendarEventsFromCompanyIdQuery, GetTimelineCalendarEventsFromCompanyIdQueryVariables>(GetTimelineCalendarEventsFromCompanyIdDocument, options);
}
export function useGetTimelineCalendarEventsFromCompanyIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetTimelineCalendarEventsFromCompanyIdQuery, GetTimelineCalendarEventsFromCompanyIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetTimelineCalendarEventsFromCompanyIdQuery, GetTimelineCalendarEventsFromCompanyIdQueryVariables>(GetTimelineCalendarEventsFromCompanyIdDocument, options);
}
export type GetTimelineCalendarEventsFromCompanyIdQueryHookResult = ReturnType<typeof useGetTimelineCalendarEventsFromCompanyIdQuery>;
export type GetTimelineCalendarEventsFromCompanyIdLazyQueryHookResult = ReturnType<typeof useGetTimelineCalendarEventsFromCompanyIdLazyQuery>;
export type GetTimelineCalendarEventsFromCompanyIdQueryResult = Apollo.QueryResult<GetTimelineCalendarEventsFromCompanyIdQuery, GetTimelineCalendarEventsFromCompanyIdQueryVariables>;
export const GetTimelineCalendarEventsFromPersonIdDocument = gql`
query GetTimelineCalendarEventsFromPersonId($personId: ID!, $page: Int!, $pageSize: Int!) {
getTimelineCalendarEventsFromPersonId(
personId: $personId
page: $page
pageSize: $pageSize
) {
...TimelineCalendarEventsWithTotalFragment
}
}
${TimelineCalendarEventsWithTotalFragmentFragmentDoc}`;
/**
* __useGetTimelineCalendarEventsFromPersonIdQuery__
*
* To run a query within a React component, call `useGetTimelineCalendarEventsFromPersonIdQuery` and pass it any options that fit your needs.
* When your component renders, `useGetTimelineCalendarEventsFromPersonIdQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetTimelineCalendarEventsFromPersonIdQuery({
* variables: {
* personId: // value for 'personId'
* page: // value for 'page'
* pageSize: // value for 'pageSize'
* },
* });
*/
export function useGetTimelineCalendarEventsFromPersonIdQuery(baseOptions: Apollo.QueryHookOptions<GetTimelineCalendarEventsFromPersonIdQuery, GetTimelineCalendarEventsFromPersonIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetTimelineCalendarEventsFromPersonIdQuery, GetTimelineCalendarEventsFromPersonIdQueryVariables>(GetTimelineCalendarEventsFromPersonIdDocument, options);
}
export function useGetTimelineCalendarEventsFromPersonIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetTimelineCalendarEventsFromPersonIdQuery, GetTimelineCalendarEventsFromPersonIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetTimelineCalendarEventsFromPersonIdQuery, GetTimelineCalendarEventsFromPersonIdQueryVariables>(GetTimelineCalendarEventsFromPersonIdDocument, options);
}
export type GetTimelineCalendarEventsFromPersonIdQueryHookResult = ReturnType<typeof useGetTimelineCalendarEventsFromPersonIdQuery>;
export type GetTimelineCalendarEventsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineCalendarEventsFromPersonIdLazyQuery>;
export type GetTimelineCalendarEventsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineCalendarEventsFromPersonIdQuery, GetTimelineCalendarEventsFromPersonIdQueryVariables>;
export const GetTimelineThreadsFromCompanyIdDocument = gql`
query GetTimelineThreadsFromCompanyId($companyId: ID!, $page: Int!, $pageSize: Int!) {
getTimelineThreadsFromCompanyId(

View File

@@ -0,0 +1,13 @@
import { gql } from '@apollo/client';
export const attendeeFragment = gql`
fragment AttendeeFragment on TimelineCalendarEventAttendee {
personId
workspaceMemberId
firstName
lastName
displayName
avatarUrl
handle
}
`;

View File

@@ -0,0 +1,19 @@
import { gql } from '@apollo/client';
import { attendeeFragment } from '@/activities/calendar/queries/fragments/attendeeFragment';
export const calendarEventFragment = gql`
fragment CalendarEventFragment on TimelineCalendarEvent {
id
title
description
location
startsAt
endsAt
isFullDay
attendees {
...AttendeeFragment
}
}
${attendeeFragment}
`;

View File

@@ -0,0 +1,13 @@
import { gql } from '@apollo/client';
import { calendarEventFragment } from '@/activities/calendar/queries/fragments/calendarEventFragment';
export const timelineCalendarEventWithTotalFragment = gql`
fragment TimelineCalendarEventsWithTotalFragment on TimelineCalendarEventsWithTotal {
totalNumberOfCalendarEvents
timelineCalendarEvents {
...CalendarEventFragment
}
}
${calendarEventFragment}
`;

View File

@@ -0,0 +1,20 @@
import { gql } from '@apollo/client';
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/queries/fragments/calendarEventFragmentWithTotalFragment';
export const getTimelineCalendarEventsFromCompanyId = gql`
query GetTimelineCalendarEventsFromCompanyId(
$companyId: ID!
$page: Int!
$pageSize: Int!
) {
getTimelineCalendarEventsFromCompanyId(
companyId: $companyId
page: $page
pageSize: $pageSize
) {
...TimelineCalendarEventsWithTotalFragment
}
}
${timelineCalendarEventWithTotalFragment}
`;

View File

@@ -0,0 +1,20 @@
import { gql } from '@apollo/client';
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/queries/fragments/calendarEventFragmentWithTotalFragment';
export const getTimelineCalendarEventsFromPersonId = gql`
query GetTimelineCalendarEventsFromPersonId(
$personId: ID!
$page: Int!
$pageSize: Int!
) {
getTimelineCalendarEventsFromPersonId(
personId: $personId
page: $page
pageSize: $pageSize
) {
...TimelineCalendarEventsWithTotalFragment
}
}
${timelineCalendarEventWithTotalFragment}
`;

View File

@@ -0,0 +1,2 @@
export const TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE = 20;
export const TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE = 50;

View File

@@ -0,0 +1,25 @@
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType('TimelineCalendarEventAttendee')
export class TimelineCalendarEventAttendee {
@Field(() => ID, { nullable: true })
personId: string;
@Field(() => ID, { nullable: true })
workspaceMemberId: string;
@Field()
firstName: string;
@Field()
lastName: string;
@Field()
displayName: string;
@Field()
avatarUrl: string;
@Field()
handle: string;
}

View File

@@ -0,0 +1,52 @@
import { ObjectType, ID, Field, registerEnumType } from '@nestjs/graphql';
import { TimelineCalendarEventAttendee } from 'src/engine/modules/calendar/dtos/timeline-calendar-event-attendee.dto';
export enum TimelineCalendarEventVisibility {
METADATA = 'METADATA',
SHARE_EVERYTHING = 'SHARE_EVERYTHING',
}
registerEnumType(TimelineCalendarEventVisibility, {
name: 'TimelineCalendarEventVisibility',
description: 'Visibility of the calendar event',
});
@ObjectType('TimelineCalendarEvent')
export class TimelineCalendarEvent {
@Field(() => ID)
id: string;
@Field()
title: string;
@Field()
isCanceled: boolean;
@Field()
isFullDay: boolean;
@Field()
startsAt: Date;
@Field()
endsAt: Date;
@Field()
description: string;
@Field()
location: string;
@Field()
conferenceSolution: string;
@Field()
conferenceUri: string;
@Field(() => [TimelineCalendarEventAttendee])
attendees: TimelineCalendarEventAttendee[];
@Field(() => TimelineCalendarEventVisibility)
visibility: TimelineCalendarEventVisibility;
}

View File

@@ -0,0 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { TimelineCalendarEvent } from 'src/engine/modules/calendar/dtos/timeline-calendar-event.dto';
@ObjectType('TimelineCalendarEventsWithTotal')
export class TimelineCalendarEventsWithTotal {
@Field(() => Int)
totalNumberOfCalendarEvents: number;
@Field(() => [TimelineCalendarEvent])
timelineCalendarEvents: TimelineCalendarEvent[];
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { UserModule } from 'src/engine/modules/user/user.module';
import { TimelineCalendarEventResolver } from 'src/engine/modules/calendar/timeline-calendar-event.resolver';
import { TimelineCalendarEventService } from 'src/engine/modules/calendar/timeline-calendar-event.service';
@Module({
imports: [WorkspaceDataSourceModule, UserModule],
exports: [],
providers: [TimelineCalendarEventResolver, TimelineCalendarEventService],
})
export class TimelineCalendarEventModule {}

View File

@@ -0,0 +1,108 @@
import { UseGuards } from '@nestjs/common';
import {
Query,
Args,
ArgsType,
Field,
ID,
Int,
Resolver,
} from '@nestjs/graphql';
import { Max } from 'class-validator';
import { User } from 'src/engine/modules/user/user.entity';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/modules/calendar/constants/calendar.constants';
import { TimelineCalendarEventsWithTotal } from 'src/engine/modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { TimelineCalendarEventService } from 'src/engine/modules/calendar/timeline-calendar-event.service';
import { UserService } from 'src/engine/modules/user/services/user.service';
import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
import { NotFoundError } from 'src/engine/filters/utils/graphql-errors.util';
@ArgsType()
class GetTimelineCalendarEventsFromPersonIdArgs {
@Field(() => ID)
personId: string;
@Field(() => Int)
page: number;
@Field(() => Int)
@Max(TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE)
pageSize: number;
}
@ArgsType()
class GetTimelineCalendarEventsFromCompanyIdArgs {
@Field(() => ID)
companyId: string;
@Field(() => Int)
page: number;
@Field(() => Int)
@Max(TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE)
pageSize: number;
}
@UseGuards(JwtAuthGuard)
@Resolver(() => TimelineCalendarEventsWithTotal)
export class TimelineCalendarEventResolver {
constructor(
private readonly timelineCalendarEventService: TimelineCalendarEventService,
private readonly userService: UserService,
) {}
@Query(() => TimelineCalendarEventsWithTotal)
async getTimelineCalendarEventsFromPersonId(
@AuthWorkspace() { id: workspaceId }: Workspace,
@AuthUser() user: User,
@Args()
{ personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs,
) {
const workspaceMember = await this.userService.loadWorkspaceMember(user);
if (!workspaceMember) {
throw new NotFoundError('Workspace member not found');
}
const timelineCalendarEvents =
await this.timelineCalendarEventService.getCalendarEventsFromPersonIds(
workspaceMember.id,
workspaceId,
[personId],
page,
pageSize,
);
return timelineCalendarEvents;
}
@Query(() => TimelineCalendarEventsWithTotal)
async getTimelineCalendarEventsFromCompanyId(
@AuthWorkspace() { id: workspaceId }: Workspace,
@AuthUser() user: User,
@Args()
{ companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs,
) {
const workspaceMember = await this.userService.loadWorkspaceMember(user);
if (!workspaceMember) {
throw new NotFoundError('Workspace member not found');
}
const timelineCalendarEvents =
await this.timelineCalendarEventService.getCalendarEventsFromCompanyId(
workspaceMember.id,
workspaceId,
companyId,
page,
pageSize,
);
return timelineCalendarEvents;
}
}

View File

@@ -0,0 +1,271 @@
import { Injectable } from '@nestjs/common';
import groupBy from 'lodash.groupBy';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/modules/calendar/constants/calendar.constants';
import { TimelineCalendarEventAttendee } from 'src/engine/modules/calendar/dtos/timeline-calendar-event-attendee.dto';
import {
TimelineCalendarEvent,
TimelineCalendarEventVisibility,
} from 'src/engine/modules/calendar/dtos/timeline-calendar-event.dto';
import { TimelineCalendarEventsWithTotal } from 'src/engine/modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { CalendarEventAttendeeObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-event-attendee.object-metadata';
type TimelineCalendarEventAttendeeWithPersonInformation =
ObjectRecord<CalendarEventAttendeeObjectMetadata> & {
personFirstName: string;
personLastName: string;
personAvatarUrl: string;
workspaceMemberFirstName: string;
workspaceMemberLastName: string;
workspaceMemberAvatarUrl: string;
};
@Injectable()
export class TimelineCalendarEventService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
async getCalendarEventsFromPersonIds(
workspaceMemberId: string,
workspaceId: string,
personIds: string[],
page: number = 1,
pageSize: number = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE,
): Promise<TimelineCalendarEventsWithTotal> {
const offset = (page - 1) * pageSize;
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarEvents: Omit<TimelineCalendarEvent, 'attendees'>[] =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT
"calendarEvent".*
FROM
${dataSourceSchema}."calendarEvent" "calendarEvent"
LEFT JOIN
${dataSourceSchema}."calendarEventAttendee" "calendarEventAttendee" ON "calendarEvent".id = "calendarEventAttendee"."calendarEventId"
LEFT JOIN
${dataSourceSchema}."person" "person" ON "calendarEventAttendee"."personId" = "person".id
WHERE
"calendarEventAttendee"."personId" = ANY($1)
GROUP BY
"calendarEvent".id
ORDER BY
"calendarEvent"."startsAt" DESC
LIMIT $2
OFFSET $3`,
[personIds, pageSize, offset],
workspaceId,
);
if (!calendarEvents) {
return {
totalNumberOfCalendarEvents: 0,
timelineCalendarEvents: [],
};
}
const calendarEventAttendees: TimelineCalendarEventAttendeeWithPersonInformation[] =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT
"calendarEventAttendee".*,
"person"."nameFirstName" as "personFirstName",
"person"."nameLastName" as "personLastName",
"person"."avatarUrl" as "personAvatarUrl",
"workspaceMember"."nameFirstName" as "workspaceMemberFirstName",
"workspaceMember"."nameLastName" as "workspaceMemberLastName",
"workspaceMember"."avatarUrl" as "workspaceMemberAvatarUrl"
FROM
${dataSourceSchema}."calendarEventAttendee" "calendarEventAttendee"
LEFT JOIN
${dataSourceSchema}."person" "person" ON "calendarEventAttendee"."personId" = "person".id
LEFT JOIN
${dataSourceSchema}."workspaceMember" "workspaceMember" ON "calendarEventAttendee"."workspaceMemberId" = "workspaceMember".id
WHERE
"calendarEventAttendee"."calendarEventId" = ANY($1)`,
[calendarEvents.map((event) => event.id)],
workspaceId,
);
const formattedCalendarEventAttendees: TimelineCalendarEventAttendee[] =
calendarEventAttendees.map((attendee) => {
const firstName =
attendee.personFirstName || attendee.workspaceMemberFirstName || '';
const lastName =
attendee.personLastName || attendee.workspaceMemberLastName || '';
const displayName =
firstName || attendee.displayName || attendee.handle;
const avatarUrl =
attendee.personAvatarUrl || attendee.workspaceMemberAvatarUrl || '';
return {
calendarEventId: attendee.calendarEventId,
personId: attendee.personId,
workspaceMemberId: attendee.workspaceMemberId,
firstName,
lastName,
displayName,
avatarUrl,
handle: attendee.handle,
};
});
const calendarEventAttendeesByEventId: {
[calendarEventId: string]: TimelineCalendarEventAttendee[];
} = groupBy(formattedCalendarEventAttendees, 'calendarEventId');
const totalNumberOfCalendarEvents: { count: number }[] =
await this.workspaceDataSourceService.executeRawQuery(
`
SELECT
COUNT(DISTINCT "calendarEventId")
FROM
${dataSourceSchema}."calendarEventAttendee" "calendarEventAttendee"
WHERE
"calendarEventAttendee"."personId" = ANY($1)
`,
[personIds],
workspaceId,
);
const timelineCalendarEvents = calendarEvents.map((event) => {
const attendees = calendarEventAttendeesByEventId[event.id] || [];
return {
...event,
attendees,
};
});
const calendarEventIdsWithWorkspaceMemberInAttendees =
await this.workspaceDataSourceService.executeRawQuery(
`
SELECT
"calendarEventId"
FROM
${dataSourceSchema}."calendarEventAttendee" "calendarEventAttendee"
WHERE
"calendarEventAttendee"."workspaceMemberId" = $1
`,
[workspaceMemberId],
workspaceId,
);
const calendarEventIdsWithWorkspaceMemberInAttendeesFormatted =
calendarEventIdsWithWorkspaceMemberInAttendees.map(
(event: { calendarEventId: string }) => event.calendarEventId,
);
const calendarEventIdsToFetchVisibilityFor = timelineCalendarEvents
.filter(
(event) =>
!calendarEventIdsWithWorkspaceMemberInAttendeesFormatted.includes(
event.id,
),
)
.map((event) => event.id);
const calendarEventIdsForWhichVisibilityIsMetadata:
| {
id: string;
}[]
| undefined = await this.workspaceDataSourceService.executeRawQuery(
`
SELECT
"calendarChannelEventAssociation"."calendarEventId" AS "id"
FROM
${dataSourceSchema}."calendarChannel" "calendarChannel"
LEFT JOIN
${dataSourceSchema}."calendarChannelEventAssociation" "calendarChannelEventAssociation" ON "calendarChannel".id = "calendarChannelEventAssociation"."calendarChannelId"
WHERE
"calendarChannelEventAssociation"."calendarEventId" = ANY($1)
AND
"calendarChannel"."visibility" = 'METADATA'
`,
[calendarEventIdsToFetchVisibilityFor],
workspaceId,
);
if (!calendarEventIdsForWhichVisibilityIsMetadata) {
throw new Error('Failed to fetch calendar event visibility');
}
const calendarEventIdsForWhichVisibilityIsMetadataMap = new Map(
calendarEventIdsForWhichVisibilityIsMetadata.map((event) => [
event.id,
TimelineCalendarEventVisibility.METADATA,
]),
);
timelineCalendarEvents.forEach((event) => {
event.visibility =
calendarEventIdsForWhichVisibilityIsMetadataMap.get(event.id) ??
TimelineCalendarEventVisibility.SHARE_EVERYTHING;
if (event.visibility === TimelineCalendarEventVisibility.METADATA) {
event.title = '';
event.description = '';
event.location = '';
event.conferenceSolution = '';
event.conferenceUri = '';
}
});
return {
totalNumberOfCalendarEvents: totalNumberOfCalendarEvents[0].count,
timelineCalendarEvents,
};
}
async getCalendarEventsFromCompanyId(
workspaceMemberId: string,
workspaceId: string,
companyId: string,
page: number = 1,
pageSize: number = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE,
): Promise<TimelineCalendarEventsWithTotal> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const personIds = await this.workspaceDataSourceService.executeRawQuery(
`
SELECT
p."id"
FROM
${dataSourceSchema}."person" p
WHERE
p."companyId" = $1
`,
[companyId],
workspaceId,
);
if (!personIds) {
return {
totalNumberOfCalendarEvents: 0,
timelineCalendarEvents: [],
};
}
const formattedPersonIds = personIds.map(
(personId: { id: string }) => personId.id,
);
const messageThreads = await this.getCalendarEventsFromPersonIds(
workspaceMemberId,
workspaceId,
formattedPersonIds,
page,
pageSize,
);
return messageThreads;
}
}

View File

@@ -9,6 +9,7 @@ import { OpenApiModule } from 'src/engine/modules/open-api/open-api.module';
import { TimelineMessagingModule } from 'src/engine/modules/messaging/timeline-messaging.module';
import { BillingModule } from 'src/engine/modules/billing/billing.module';
import { HealthModule } from 'src/engine/modules/health/health.module';
import { TimelineCalendarEventModule } from 'src/engine/modules/calendar/timeline-calendar-event.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
@@ -26,6 +27,7 @@ import { ClientConfigModule } from './client-config/client-config.module';
OpenApiModule,
RefreshTokenModule,
TimelineMessagingModule,
TimelineCalendarEventModule,
UserModule,
WorkspaceModule,
],
@@ -34,6 +36,7 @@ import { ClientConfigModule } from './client-config/client-config.module';
AuthModule,
FeatureFlagModule,
TimelineMessagingModule,
TimelineCalendarEventModule,
UserModule,
WorkspaceModule,
],

View File

@@ -1,12 +1,10 @@
import { ObjectType, Field, ID } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { TimelineThreadParticipant } from 'src/engine/modules/messaging/dtos/timeline-thread-participant.dto';
@ObjectType('TimelineThread')
export class TimelineThread {
@IDField(() => ID)
@Field(() => ID)
id: string;
@Field()