Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions src/app/api/comments/comment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AttachmentsService } from '@/app/api/attachments/attachments.service'
import { PublicCommentSerializer } from '@/app/api/comments/public/public.serializer'
import { sendCommentCreateNotifications } from '@/jobs/notifications'
import { sendReplyCreateNotifications } from '@/jobs/notifications/send-reply-create-notifications'
import { InitiatedEntity } from '@/types/common'
import { InitiatedEntity, TempClientFilter } from '@/types/common'
import { CreateAttachmentRequestSchema } from '@/types/dto/attachments.dto'
import { CommentsPublicFilterType, CommentWithAttachments, CreateComment, UpdateComment } from '@/types/dto/comment.dto'
import { DISPATCHABLE_EVENT } from '@/types/webhook'
Expand Down Expand Up @@ -463,9 +463,10 @@ export class CommentService extends BaseService {
}
}

protected getClientOrCompanyAssigneeFilter(includeViewer: boolean = true): Prisma.TaskWhereInput {
protected getClientOrCompanyAssigneeFilter(includeAssociatedTask: boolean = true): Prisma.TaskWhereInput {
const clientId = z.string().uuid().parse(this.user.clientId)
const companyId = z.string().uuid().parse(this.user.companyId)
const isCuPortal = !this.user.internalUserId && (clientId || companyId)

const filters = []

Expand All @@ -476,29 +477,32 @@ export class CommentService extends BaseService {
// Get company tasks for the client's companyId
{ companyId, clientId: null },
)
if (includeViewer)
filters.push(
// Get tasks that includes the client as a viewer
{
associations: {
hasSome: [{ clientId, companyId }, { companyId }],
},
if (includeAssociatedTask) {
const tempClientFilter: TempClientFilter = {
associations: {
hasSome: [{ clientId, companyId }, { companyId }],
},
)
}
if (isCuPortal) tempClientFilter.isShared = true
// Get tasks that includes the client as a association
filters.push(tempClientFilter)
}
} else if (companyId) {
filters.push(
// Get only company tasks for the client's companyId
{ clientId: null, companyId },
)
if (includeViewer)
filters.push(
// Get tasks that includes the company as a viewer
{
associations: {
hasSome: [{ companyId }],
},

// Get tasks that includes the company as a association
if (includeAssociatedTask) {
const tempCompanyFilter: TempClientFilter = {
associations: {
hasSome: [{ companyId }],
},
)
}
if (isCuPortal) tempCompanyFilter.isShared = true
filters.push(tempCompanyFilter)
}
}
return filters.length > 0 ? { OR: filters } : {}
} //Repeated twice because taskSharedService is an abstract class.
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/tasks/public/public.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class PublicTasksService extends TasksSharedService {
if (!validatedIds.internalUserId) {
throw new APIError(httpStatus.BAD_REQUEST, `Task cannot be created with viewers if its not assigned to an IU.`)
}
viewers = await this.validateViewers(data.associations)
viewers = await this.validateAssociations(data.associations)
console.info('PublicTasksService#createTask | Associations validated for task:', viewers)
}

Expand Down Expand Up @@ -291,7 +291,7 @@ export class PublicTasksService extends TasksSharedService {
if (associationsResetCondition || !associations?.length) {
associations = [] // reset associations to [] if task is not reassigned to IU.
} else if (associations?.length) {
associations = await this.validateViewers(associations)
associations = await this.validateAssociations(associations)
}
}
return associations
Expand Down
4 changes: 3 additions & 1 deletion src/app/api/tasks/subtasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Assignable {
clientId: string | null
companyId: string | null
associations: JsonValue[]
isShared: boolean
}

export class SubtaskService extends BaseService {
Expand Down Expand Up @@ -136,7 +137,8 @@ export class SubtaskService extends BaseService {
(task.clientId === null && task.companyId === this.user.companyId) ||
(viewer &&
(!viewer?.clientId || viewer?.clientId === this.user.clientId) &&
viewer.companyId === this.user.companyId)
viewer.companyId === this.user.companyId &&
task.isShared)
)
})
} else {
Expand Down
11 changes: 7 additions & 4 deletions src/app/api/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class TasksService extends TasksSharedService {
`Task cannot be created and shared with associations if its not assigned to an IU.`,
)
}
associations = await this.validateViewers(data.associations)
associations = await this.validateAssociations(data.associations)
console.info('TasksService#createTask | Associations validated for task:', associations)
}

Expand Down Expand Up @@ -368,13 +368,15 @@ export class TasksService extends TasksSharedService {
let associations: Associations = AssociationsSchema.parse(prevTask.associations)

// check if current or previous assignee is a client or company
const viewersResetCondition = shouldUpdateUserIds ? !!clientId || !!companyId : prevTask.clientId || prevTask.companyId
const associationsResetCondition = shouldUpdateUserIds
? !!clientId || !!companyId
: prevTask.clientId || prevTask.companyId
if (data.associations) {
// only update of associations attribute is available. No associations in payload attribute means the data remains as it is in DB.
if (viewersResetCondition || !data.associations?.length) {
if (associationsResetCondition || !data.associations?.length) {
associations = [] // reset associations to [] if task is not reassigned to IU.
} else if (data.associations?.length) {
associations = await this.validateViewers(data.associations)
associations = await this.validateAssociations(data.associations)
}
}

Expand Down Expand Up @@ -679,6 +681,7 @@ export class TasksService extends TasksSharedService {
companyId: true,
internalUserId: true,
associations: true,
isShared: true,
},
}),
) as Promise<AncestorTaskResponse>[],
Expand Down
46 changes: 25 additions & 21 deletions src/app/api/tasks/tasksShared.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { maxSubTaskDepth } from '@/constants/tasks'
import { MAX_FETCH_ASSIGNEE_COUNT } from '@/constants/users'
import { InternalUsers, Uuid } from '@/types/common'
import { InternalUsers, TempClientFilter, Uuid } from '@/types/common'
import { CreateAttachmentRequestSchema } from '@/types/dto/attachments.dto'
import { CreateTaskRequest, CreateTaskRequestSchema, Associations } from '@/types/dto/tasks.dto'
import { getFileNameFromPath } from '@/utils/attachmentUtils'
Expand Down Expand Up @@ -29,7 +29,7 @@ export abstract class TasksSharedService extends BaseService {
* If user is a client, return filter for just the tasks assigned to this clientId.
* If user is a client and has a companyId, return filter for just the tasks assigned to this clientId `OR` to this companyId
*/
protected buildTaskPermissions(id?: string, includeViewer: boolean = true) {
protected buildTaskPermissions(id?: string, includeAssociatedTask: boolean = true) {
const user = this.user

// Default filters
Expand All @@ -39,15 +39,16 @@ export abstract class TasksSharedService extends BaseService {
}

if (user.clientId || user.companyId) {
filters = { ...filters, ...this.getClientOrCompanyAssigneeFilter(includeViewer) }
filters = { ...filters, ...this.getClientOrCompanyAssigneeFilter(includeAssociatedTask) }
}

return filters
}

protected getClientOrCompanyAssigneeFilter(includeViewer: boolean = true): Prisma.TaskWhereInput {
protected getClientOrCompanyAssigneeFilter(includeAssociatedTask: boolean = true): Prisma.TaskWhereInput {
const clientId = z.string().uuid().safeParse(this.user.clientId).data
const companyId = z.string().uuid().parse(this.user.companyId)
const isCuPortal = !this.user.internalUserId && (clientId || companyId)

const filters = []

Expand All @@ -58,29 +59,32 @@ export abstract class TasksSharedService extends BaseService {
// Get company tasks for the client's companyId
{ companyId, clientId: null },
)
if (includeViewer)
filters.push(
// Get tasks that includes the client as a viewer
{
associations: {
hasSome: [{ clientId, companyId }, { companyId }],
},

// Get tasks that includes the client as a association
if (includeAssociatedTask) {
const tempClientFilter: TempClientFilter = {
associations: {
hasSome: [{ clientId, companyId }, { companyId }],
},
)
}
if (isCuPortal) tempClientFilter.isShared = true
filters.push(tempClientFilter)
}
} else if (companyId) {
filters.push(
// Get only company tasks for the client's companyId
{ clientId: null, companyId },
)
if (includeViewer)
filters.push(
// Get tasks that includes the company as a viewer
{
associations: {
hasSome: [{ companyId }],
},
if (includeAssociatedTask) {
const tempCompanyFilter: TempClientFilter = {
associations: {
hasSome: [{ companyId }],
},
)
}
if (isCuPortal) tempCompanyFilter.isShared = true
// Get tasks that includes the company as a viewer
filters.push(tempCompanyFilter)
}
}
return filters.length > 0 ? { OR: filters } : {}
}
Expand Down Expand Up @@ -357,7 +361,7 @@ export abstract class TasksSharedService extends BaseService {
return { completedBy: null, completedByUserType: null, workflowStateStatus: workflowState.type }
}

protected async validateViewers(associations: Associations) {
protected async validateAssociations(associations: Associations) {
if (!associations?.length) return []
const association = associations[0]
try {
Expand Down
1 change: 1 addition & 0 deletions src/app/detail/ui/NewTaskCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ export const NewTaskCard = ({
{showShareToggle && (
<CopilotToggle
label="Share with client"
disabled={!previewMode}
onChange={() => {
setIsShared(!isShared)
handleFieldChange('isShared', !isShared)
Expand Down
10 changes: 8 additions & 2 deletions src/app/detail/ui/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ export const Sidebar = ({
hideIusList
name="Set related to"
onChange={handleTaskAssociationChange}
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow visibility change in preview mode
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow association change in preview mode
initialValue={taskAssociationValue || undefined}
buttonContent={
<SelectorButton
Expand Down Expand Up @@ -644,7 +644,13 @@ export const Sidebar = ({
{showShareToggle && (
<>
<Divider sx={{ borderColor: (theme) => theme.color.borders.border, height: '1px' }} />
<CopilotToggle label="Share with client" onChange={handleTaskShared} checked={isTaskShared} className="pt-4" />
<CopilotToggle
label="Share with client"
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow task share in preview mode
onChange={handleTaskShared}
checked={isTaskShared}
className="pt-4"
/>
</>
)}
</AppMargin>
Expand Down
Loading
Loading