Skip to content
Merged
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
144 changes: 113 additions & 31 deletions apps/lfx-one/src/server/services/org-contributions.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { VALKEY_CACHE } from '@lfx-one/shared/constants';
import type {
ContributionSource,
ContributionsCommitSortColumn,
Expand All @@ -17,6 +18,7 @@ import type {
} from '@lfx-one/shared/interfaces';

import { SnowflakeService } from './snowflake.service';
import { withOrgCache } from './valkey.service';

const PLATINUM_TABLE = 'ANALYTICS.PLATINUM_LFX_ONE.ORG_CODE_CONTRIBUTIONS';

Expand Down Expand Up @@ -90,33 +92,66 @@ export class OrgContributionsService {
const repoPagination = viewAwarePagination(query, 'repositories');
const commitPagination = viewAwarePagination(query, 'commits');

const [kpiResult, repoResult, commitResult, projectOptions, employeeOptions] = await Promise.all([
this.fetchKpis(accountId, scope, kpiSearch),
this.fetchRepositories(accountId, scope, repoSearch, query, repoPagination),
this.fetchCommits(accountId, scope, commitSearch, query, commitPagination),
this.fetchProjectOptions(accountId, query.dateRange),
this.fetchEmployeeOptions(accountId, query.dateRange),
]);

const kpis = mapKpis(kpiResult.rows[0]);
const totalRecords = repoResult.rows.length > 0 ? repoResult.rows[0].TOTAL_RECORDS : 0;
const repositories = query.view === 'repositories' ? repoResult.rows.map(mapRepoRow) : [];
const commitsTotalRecords = commitResult.rows.length > 0 ? commitResult.rows[0].TOTAL_RECORDS : 0;
const commits = query.view === 'commits' ? commitResult.rows.map(mapCommitRow) : [];
const raw = await withOrgCache(
accountId,
`contributions:${contributionsSignature(query)}`,
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchContributionsRaw(accountId, query, scope, kpiSearch, repoSearch, commitSearch, repoPagination, commitPagination),
isContributionsRaw
);

const kpis = mapKpis(raw.kpiRows[0]);
const totalRecords = raw.repoRows.length > 0 ? raw.repoRows[0].TOTAL_RECORDS : 0;
const repositories = query.view === 'repositories' ? raw.repoRows.map(mapRepoRow) : [];
const commitsTotalRecords = raw.commitRows.length > 0 ? raw.commitRows[0].TOTAL_RECORDS : 0;
const commits = query.view === 'commits' ? raw.commitRows.map(mapCommitRow) : [];

return {
accountId,
dateRange: query.dateRange,
kpis,
repositories,
commits,
projectOptions,
employeeOptions,
projectOptions: raw.projectOptionRows.map(mapProjectOption),
employeeOptions: raw.employeeOptionRows.map(mapEmployeeOption),
totalRecords,
commitsTotalRecords,
};
}

private async fetchContributionsRaw(
accountId: string,
query: OrgContributionsQuery,
scope: ScopeFilters,
kpiSearch: SearchFilter,
repoSearch: SearchFilter,
commitSearch: SearchFilter,
repoPagination: ViewAwarePagination,
commitPagination: ViewAwarePagination
): Promise<{
kpiRows: ContributionsKpiRow[];
repoRows: ContributionsRepoRow[];
commitRows: ContributionsCommitRow[];
projectOptionRows: ContributionsProjectOptionRow[];
employeeOptionRows: ContributionsEmployeeOptionRow[];
}> {
const [kpiResult, repoResult, commitResult, projectResult, employeeResult] = await Promise.all([
this.fetchKpis(accountId, scope, kpiSearch),
this.fetchRepositories(accountId, scope, repoSearch, query, repoPagination),
this.fetchCommits(accountId, scope, commitSearch, query, commitPagination),
this.fetchProjectOptionRows(accountId, query.dateRange),
this.fetchEmployeeOptionRows(accountId, query.dateRange),
]);

return {
kpiRows: kpiResult.rows,
repoRows: repoResult.rows,
commitRows: commitResult.rows,
projectOptionRows: projectResult.rows,
employeeOptionRows: employeeResult.rows,
};
}

private async fetchKpis(accountId: string, scope: ScopeFilters, kpiSearch: SearchFilter): Promise<{ rows: ContributionsKpiRow[] }> {
const sql = `
SELECT
Expand Down Expand Up @@ -235,7 +270,7 @@ export class OrgContributionsService {
return this.snowflakeService.execute<ContributionsCommitRow>(sql, [accountId, ...scope.binds, ...commitSearch.binds]);
}

private async fetchProjectOptions(accountId: string, dateRange: ContributionsDateRange): Promise<OrgContributionProjectOption[]> {
private async fetchProjectOptionRows(accountId: string, dateRange: ContributionsDateRange): Promise<{ rows: ContributionsProjectOptionRow[] }> {
const datePredicate = dateRangePredicate(dateRange);
const sql = `
SELECT
Expand All @@ -251,17 +286,10 @@ export class OrgContributionsService {
GROUP BY project_id
ORDER BY commits DESC, project_name ASC
`;
const result = await this.snowflakeService.execute<ContributionsProjectOptionRow>(sql, [accountId]);
return result.rows.map((row) => ({
slug: row.PROJECT_SLUG ?? row.PROJECT_ID,
projectId: row.PROJECT_ID,
name: row.PROJECT_NAME ?? row.PROJECT_ID,
commits: row.COMMITS ?? 0,
parentSlug: row.PARENT_SLUG,
}));
return this.snowflakeService.execute<ContributionsProjectOptionRow>(sql, [accountId]);
}

private async fetchEmployeeOptions(accountId: string, dateRange: ContributionsDateRange): Promise<OrgContributionEmployeeOption[]> {
private async fetchEmployeeOptionRows(accountId: string, dateRange: ContributionsDateRange): Promise<{ rows: ContributionsEmployeeOptionRow[] }> {
const datePredicate = dateRangePredicate(dateRange);
const sql = `
SELECT
Expand All @@ -275,12 +303,7 @@ export class OrgContributionsService {
GROUP BY member_id
ORDER BY commits DESC, member_display_name ASC
`;
const result = await this.snowflakeService.execute<ContributionsEmployeeOptionRow>(sql, [accountId]);
return result.rows.map((row) => ({
id: row.MEMBER_ID,
displayName: row.MEMBER_DISPLAY_NAME ?? row.MEMBER_ID,
commits: row.COMMITS ?? 0,
}));
return this.snowflakeService.execute<ContributionsEmployeeOptionRow>(sql, [accountId]);
}
}

Expand Down Expand Up @@ -415,6 +438,65 @@ function mapKpis(row: ContributionsKpiRow | undefined): OrgContributionsKpis {
};
}

function mapProjectOption(row: ContributionsProjectOptionRow): OrgContributionProjectOption {
return {
slug: row.PROJECT_SLUG ?? row.PROJECT_ID,
projectId: row.PROJECT_ID,
name: row.PROJECT_NAME ?? row.PROJECT_ID,
commits: row.COMMITS ?? 0,
parentSlug: row.PARENT_SLUG,
};
}

function mapEmployeeOption(row: ContributionsEmployeeOptionRow): OrgContributionEmployeeOption {
return {
id: row.MEMBER_ID,
displayName: row.MEMBER_DISPLAY_NAME ?? row.MEMBER_ID,
commits: row.COMMITS ?? 0,
};
}

/** Deterministic, key-safe cache-key suffix covering every query field that changes the SQL (filter arrays sorted so member order never fragments the key); base64url keeps it to `[A-Za-z0-9_-]`. */
function contributionsSignature(query: OrgContributionsQuery): string {
const parts = [
query.dateRange,
query.view,
query.search,
query.sort,
query.dir,
query.commitSort,
query.commitDir,
query.page,
query.size,
[...query.projects].sort().join(','),
[...query.employees].sort().join(','),
];
return Buffer.from(JSON.stringify(parts), 'utf8').toString('base64url');
}

function isContributionsRaw(value: unknown): boolean {
const v = value as { kpiRows?: unknown; repoRows?: unknown; commitRows?: unknown; projectOptionRows?: unknown; employeeOptionRows?: unknown } | null;
return (
!!v &&
isRowArray(v.kpiRows, 'PROJECTS_WITH_ACTIVITY', 'REPOSITORIES', 'COMMITS') &&
isRowArray(v.repoRows, 'REPOSITORY_URL') &&
isRowArray(v.commitRows, 'COMMIT_ID') &&
isRowArray(v.projectOptionRows, 'PROJECT_ID') &&
isRowArray(v.employeeOptionRows, 'MEMBER_ID')
);
}

/** Array guard that validates every element so a single corrupt/legacy row degrades the whole entry to a cache miss: each element must be a non-null object carrying all required contract keys. */
function isRowArray(value: unknown, ...requiredKeys: string[]): boolean {
return (
Array.isArray(value) &&
value.every((row) => {
if (row === null || typeof row !== 'object' || Array.isArray(row)) return false;
return requiredKeys.every((key) => key in (row as Record<string, unknown>));
})
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function mapRepoRow(row: ContributionsRepoRow): OrgContributionRepoRow {
return {
repositoryId: row.REPOSITORY_URL,
Expand Down
77 changes: 72 additions & 5 deletions apps/lfx-one/src/server/services/org-involvement.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
OrgInvolvementMaintainersMonthlyResponse,
OrgTrainingEnrollmentsResponse,
} from '@lfx-one/shared';
import { VALKEY_CACHE } from '@lfx-one/shared/constants';

import { SnowflakeService } from './snowflake.service';
import { withOrgCache } from './valkey.service';

const formatMonthLabel = (date: Date): string => date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });

Expand Down Expand Up @@ -83,6 +85,71 @@ export class OrgInvolvementService {
* an empty state instead of treating the call as an error.
*/
public async getFoundationCoverage(accountId: string): Promise<OrgFoundationCoverageResponse> {
return withOrgCache(
accountId,
'coverage',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchFoundationCoverage(accountId),
OrgInvolvementService.hasAccountId
);
}

public async getContributorsMonthly(accountId: string): Promise<OrgInvolvementContributorsMonthlyResponse> {
return withOrgCache(
accountId,
'contributors',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchContributorsMonthly(accountId),
OrgInvolvementService.hasAccountId
);
}

public async getMaintainersMonthly(accountId: string): Promise<OrgInvolvementMaintainersMonthlyResponse> {
return withOrgCache(
accountId,
'maintainers',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchMaintainersMonthly(accountId),
OrgInvolvementService.hasAccountId
);
}

public async getEventAttendanceMonthly(accountId: string): Promise<OrgInvolvementEventAttendanceMonthlyResponse> {
return withOrgCache(
accountId,
'events',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchEventAttendanceMonthly(accountId),
OrgInvolvementService.hasAccountId
);
}

public async getCertifiedEmployeesMonthly(accountId: string): Promise<OrgInvolvementCertifiedEmployeesMonthlyResponse> {
return withOrgCache(
accountId,
'certs',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchCertifiedEmployeesMonthly(accountId),
OrgInvolvementService.hasAccountId
);
}

public async getTrainingEnrollments(accountId: string): Promise<OrgTrainingEnrollmentsResponse> {
return withOrgCache(
accountId,
'training',
VALKEY_CACHE.ORG_LENS_SNOWFLAKE_TTL_SECONDS,
() => this.fetchTrainingEnrollments(accountId),
OrgInvolvementService.hasAccountId
);
}

// Rejects a corrupt/legacy entry (degrade to a miss); every response in this service carries `accountId`.
private static hasAccountId(value: unknown): boolean {
return value !== null && typeof value === 'object' && !Array.isArray(value) && typeof (value as { accountId?: unknown }).accountId === 'string';
}

private async fetchFoundationCoverage(accountId: string): Promise<OrgFoundationCoverageResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down Expand Up @@ -112,7 +179,7 @@ export class OrgInvolvementService {
};
}

public async getContributorsMonthly(accountId: string): Promise<OrgInvolvementContributorsMonthlyResponse> {
private async fetchContributorsMonthly(accountId: string): Promise<OrgInvolvementContributorsMonthlyResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down Expand Up @@ -140,7 +207,7 @@ export class OrgInvolvementService {
};
}

public async getMaintainersMonthly(accountId: string): Promise<OrgInvolvementMaintainersMonthlyResponse> {
private async fetchMaintainersMonthly(accountId: string): Promise<OrgInvolvementMaintainersMonthlyResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down Expand Up @@ -173,7 +240,7 @@ export class OrgInvolvementService {
};
}

public async getEventAttendanceMonthly(accountId: string): Promise<OrgInvolvementEventAttendanceMonthlyResponse> {
private async fetchEventAttendanceMonthly(accountId: string): Promise<OrgInvolvementEventAttendanceMonthlyResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down Expand Up @@ -217,7 +284,7 @@ export class OrgInvolvementService {
};
}

public async getCertifiedEmployeesMonthly(accountId: string): Promise<OrgInvolvementCertifiedEmployeesMonthlyResponse> {
private async fetchCertifiedEmployeesMonthly(accountId: string): Promise<OrgInvolvementCertifiedEmployeesMonthlyResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down Expand Up @@ -248,7 +315,7 @@ export class OrgInvolvementService {
};
}

public async getTrainingEnrollments(accountId: string): Promise<OrgTrainingEnrollmentsResponse> {
private async fetchTrainingEnrollments(accountId: string): Promise<OrgTrainingEnrollmentsResponse> {
const query = `
SELECT
ACCOUNT_ID,
Expand Down
Loading
Loading