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
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ workflows:
only:
- develop
- PM-4931
- skill-statistics
tags:
only: /^dev-.*/

Expand Down
1 change: 1 addition & 0 deletions sql/reports/statistics/expert-skills/category-members.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ WITH category_wins AS (
JOIN skills.source_type sest
ON sest.id = se.source_type_id
WHERE sk.category_id = $1::uuid
AND NOT (se.user_id::text = ANY($3))
AND (
LOWER(set_t.name) IN (
'challenge_win',
Expand Down
2 changes: 2 additions & 0 deletions sql/reports/statistics/expert-skills/category-stats.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ member_counts AS (
ON sk.id = us.skill_id
AND sk.deleted_at IS NULL
WHERE sk.category_id = ANY($1::uuid[])
AND NOT (us.user_id::text = ANY($2))
GROUP BY sk.category_id
),
win_events AS (
Expand All @@ -37,6 +38,7 @@ win_events AS (
JOIN skills.source_type sest
ON sest.id = se.source_type_id
WHERE sk.category_id = ANY($1::uuid[])
AND NOT (se.user_id::text = ANY($2))
AND (
LOWER(set_t.name) IN (
'challenge_win',
Expand Down
12 changes: 12 additions & 0 deletions src/statistics/expert-skills-statistics.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NotFoundException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { DbService } from "../db/db.service";
import { SqlLoaderService } from "../common/sql-loader.service";
import { ExpertSkillsStatisticsService } from "./expert-skills-statistics.service";
Expand All @@ -10,9 +11,18 @@ describe("ExpertSkillsStatisticsService", () => {
const sql = {
load: jest.fn().mockReturnValue("SELECT expert skills"),
};
const config = {
get: jest.fn((key: string, defaultValue?: string) => {
if (key === "REPORTS_EXCLUDED_USER_IDS") {
return '["22838965", "8547899"]';
}
return defaultValue;
}),
};
const service = new ExpertSkillsStatisticsService(
db as unknown as DbService,
sql as unknown as SqlLoaderService,
config as unknown as ConfigService,
);

beforeEach(() => {
Expand Down Expand Up @@ -63,6 +73,7 @@ describe("ExpertSkillsStatisticsService", () => {
"481b5ebc-2fe6-45ed-a90c-736936d458d7",
"1f5ed3e8-8d22-44ea-b75d-ea85147a04da",
],
["22838965", "8547899"],
]);
expect(result[0]).toEqual(
expect.objectContaining({
Expand Down Expand Up @@ -126,6 +137,7 @@ describe("ExpertSkillsStatisticsService", () => {
expect(db.query).toHaveBeenNthCalledWith(2, "SELECT expert skills", [
"481b5ebc-2fe6-45ed-a90c-736936d458d7",
100,
["22838965", "8547899"],
]);
expect(result).toEqual([
{
Expand Down
44 changes: 42 additions & 2 deletions src/statistics/expert-skills-statistics.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import {
alpha3ToCountryName,
toAlpha2CountryCode,
Expand Down Expand Up @@ -76,10 +77,45 @@ function formatMemberName(

@Injectable()
export class ExpertSkillsStatisticsService {
private readonly excludedUserIds: string[];

constructor(
private readonly db: DbService,
private readonly sql: SqlLoaderService,
) {}
private readonly config: ConfigService,
) {
this.excludedUserIds = this.parseListConfig(
"REPORTS_EXCLUDED_USER_IDS",
"[]",
);
}

// Accepts either a JSON array string ('["1","2"]') or a comma-separated list.
private parseListConfig(key: string, defaultValue: string): string[] {
const raw = (this.config.get<string>(key, defaultValue) ?? "").trim();

if (!raw) {
return [];
}

try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
return parsed
.map((item) =>
typeof item === "number" ? String(item) : String(item ?? "").trim(),
)
.filter(Boolean);
}
} catch {
// ignore JSON parse failure and fall back to comma-separated values
}

return raw
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}

async getCategories() {
const categories = await this.loadCategories();
Expand Down Expand Up @@ -122,6 +158,7 @@ export class ExpertSkillsStatisticsService {
const rows = await this.db.query<MemberRow>(q, [
category.id,
MEMBERS_LIMIT,
this.excludedUserIds,
]);

return rows.map((row) => {
Expand Down Expand Up @@ -180,7 +217,10 @@ export class ExpertSkillsStatisticsService {
const q = this.sql.load(
"reports/statistics/expert-skills/category-stats.sql",
);
const rows = await this.db.query<CategoryStatsRow>(q, [categoryIds]);
const rows = await this.db.query<CategoryStatsRow>(q, [
categoryIds,
this.excludedUserIds,
]);

return new Map(rows.map((row) => [row.id, row]));
}
Expand Down
3 changes: 3 additions & 0 deletions src/statistics/statistics-expert-skills.sql.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ describe("Expert skills statistics SQL", () => {
expect(sql).toContain("challenge_win");
expect(sql).toContain("gig_completion");
expect(sql).toContain("ts.rn <= 3");
expect(sql).toContain("NOT (us.user_id::text = ANY($2))");
expect(sql).toContain("NOT (se.user_id::text = ANY($2))");
expect(sql).not.toContain("NOT ILIKE 'Test Cat%'");
});

Expand All @@ -37,6 +39,7 @@ describe("Expert skills statistics SQL", () => {
expect(sql).toContain("JOIN members.member m");
expect(sql).toContain('members."memberMaxRating"');
expect(sql).toContain("ORDER BY cw.wins DESC, m.handle ASC");
expect(sql).toContain("NOT (se.user_id::text = ANY($3))");
expect(sql).toContain("LIMIT $2");
});
});
Loading