From 5afcc2d8ec5bb53d735be7f23a43aef6883c2df3 Mon Sep 17 00:00:00 2001 From: Nikolai Giman Date: Fri, 8 May 2026 22:56:01 +0200 Subject: [PATCH] fix: goals archive exclude and permissions check for drilldown --- .../tv-modules/analytics/AnalyticsManager.ts | 37 +++++++++++-------- .../analytics/AnalyticsRepository.ts | 22 +++++++---- .../workload/WorkloadByAssigneeSection.ts | 1 + 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/api/src/tv-modules/analytics/AnalyticsManager.ts b/api/src/tv-modules/analytics/AnalyticsManager.ts index a89025f..6b493cf 100644 --- a/api/src/tv-modules/analytics/AnalyticsManager.ts +++ b/api/src/tv-modules/analytics/AnalyticsManager.ts @@ -29,14 +29,14 @@ export class AnalyticsManager { } async getAccessibleGoalIds(organizationId: number): Promise { - const userData = this.user.getUserData() - if (!userData?.id || !userData?.email) return [] + return this.fetchGoalIds(organizationId, [GoalPermissions.ANALYTICS_CAN_VIEW]) + } - const ids = await this.user.organizationManager.isCurrentUserOrgOwner(organizationId) - ? await this.repository.fetchAllGoalIdsInOrg(organizationId) - : await this.fetchMemberAccessibleGoalIds(userData.id, userData.email, organizationId) - - return this.applyTokenFilter(ids) + async getDrillDownGoalIds(organizationId: number): Promise { + return this.fetchGoalIds(organizationId, [ + GoalPermissions.ANALYTICS_CAN_VIEW, + GoalPermissions.TASKS_CAN_WATCH_DETAILS, + ]) } async buildSections(params: AnalyticsArgBuildSections): Promise { @@ -96,7 +96,7 @@ export class AnalyticsManager { return { sectionId, tasks: [], total: 0 } } - const allAccessible = await this.getAccessibleGoalIds(organizationId) + const allAccessible = await this.getDrillDownGoalIds(organizationId) const accessibleGoalIds = this.narrowToScope(allAccessible, scope) const ctx: BuilderContext = { @@ -116,13 +116,20 @@ export class AnalyticsManager { return { sectionId, tasks, total: tasks.length } } - private async fetchMemberAccessibleGoalIds(userId: number, email: string, organizationId: number): Promise { - return this.repository.fetchGoalIdsWithPermission( - userId, - email, - organizationId, - GoalPermissions.ANALYTICS_CAN_VIEW, - ) + private async fetchGoalIds(organizationId: number, permissions: string[]): Promise { + const userData = this.user.getUserData() + if (!userData?.id || !userData?.email) return [] + + const ids = await this.user.organizationManager.isCurrentUserOrgOwner(organizationId) + ? await this.repository.fetchAllGoalIdsInOrg(organizationId) + : await this.repository.fetchGoalIdsWithPermissions( + userData.id, + userData.email, + organizationId, + permissions, + ) + + return this.applyTokenFilter(ids) } private applyTokenFilter(ids: number[]): number[] { diff --git a/api/src/tv-modules/analytics/AnalyticsRepository.ts b/api/src/tv-modules/analytics/AnalyticsRepository.ts index 84068c8..909e089 100644 --- a/api/src/tv-modules/analytics/AnalyticsRepository.ts +++ b/api/src/tv-modules/analytics/AnalyticsRepository.ts @@ -1,4 +1,4 @@ -import { eq, inArray, sql, type SQL } from 'drizzle-orm' +import { and, eq, inArray, sql, type SQL } from 'drizzle-orm' import { GoalsSchema } from 'taskview-db-schemas' import { Database } from '../../modules/db' import { callWithCatch } from '../../utils/helpers' @@ -74,24 +74,30 @@ export class AnalyticsRepository { this.db.dbDrizzle .select({ id: GoalsSchema.id }) .from(GoalsSchema) - .where(eq(GoalsSchema.organizationId, organizationId)), + .where(and( + eq(GoalsSchema.organizationId, organizationId), + eq(GoalsSchema.archive, 0), + )), ) return (result ?? []).map(r => r.id).filter((id): id is number => id !== null) } - async fetchGoalIdsWithPermission( + async fetchGoalIdsWithPermissions( userId: number, email: string, organizationId: number, - permissionName: string, + permissionNames: string[], ): Promise { + if (permissionNames.length === 0) return [] + const result = await this.db.dbDrizzle.execute<{ id: number }>(sql` select g.id from tasks.goals g where g.organization_id = ${organizationId} + and g.archive = 0 and ( g.owner = ${userId} - or exists ( - select 1 + or ( + select count(distinct p.name) from collaboration.users cu join collaboration.users_to_goals utg on utg.user_id = cu.id and utg.goal_id = g.id join collaboration.users_to_roles utr on utr.user_id = cu.id @@ -99,8 +105,8 @@ export class AnalyticsRepository { join collaboration.permissions_to_role ptr on ptr.role_id = r.id join tv_auth.permissions p on p.id = ptr.permission_id where cu.email = ${email} - and p.name = ${permissionName} - ) + and p.name = any(${sql`ARRAY[${sql.join(permissionNames.map(n => sql`${n}`), sql`, `)}]::text[]`}) + ) = ${permissionNames.length} ) `) return result.rows.map(r => Number(r.id)).filter(id => Number.isInteger(id)) diff --git a/api/src/tv-modules/analytics/sections/workload/WorkloadByAssigneeSection.ts b/api/src/tv-modules/analytics/sections/workload/WorkloadByAssigneeSection.ts index c9a2400..65b4a33 100644 --- a/api/src/tv-modules/analytics/sections/workload/WorkloadByAssigneeSection.ts +++ b/api/src/tv-modules/analytics/sections/workload/WorkloadByAssigneeSection.ts @@ -88,6 +88,7 @@ export class WorkloadByAssigneeSection implements SectionBuilder { no_priority: 'null', } const priorityFilter = priorityByDataset[arg.datasetId] + if (priorityFilter === undefined) return [] return ctx.repository.fetchOpenTasksAssignedWithPriority( ctx.accessibleGoalIds,