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
8 changes: 6 additions & 2 deletions apps/api/src/services/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const projectService = {
*/
async fetchGithubProjects(
filters: Partial<FilterProps> = {},
options: Partial<OptionsTypesProps> = {}
options: Partial<OptionsTypesProps> = {},
): Promise<RepositoryProps[]> {
const queryParts: string[] = [];

Expand Down Expand Up @@ -77,6 +77,10 @@ export const projectService = {
primaryLanguage {
name
}
stargazerCount
forkCount
pushedAt
createdAt
}
}
repositoryCount
Expand All @@ -86,7 +90,7 @@ export const projectService = {
{
searchQuery: searchQueryString,
first: options.per_page || 100,
}
},
);

return response.search.nodes;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ui/FiltersContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function FiltersContainer() {
setProjectsNotFound(true);
return;
}
const modifiedProjects = convertApiOutputToUserOutput(projects, filters);
const modifiedProjects = convertApiOutputToUserOutput(projects);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

LGTM — call site aligned with the new single-arg converter.

Dropping filters here is consistent with the refactored convertApiOutputToUserOutput(response) signature and is the core of the fix: unselected filters will now reflect each project's actual values rather than being blank/filter-derived.

One small note unrelated to this line: if getProjects throws, setLoading(true) will remain set because the catch block on line 54–56 doesn't reset it. Worth considering a finally { setLoading(false); } so the UI doesn't get stuck on error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ui/FiltersContainer.tsx` at line 49, The call to
convertApiOutputToUserOutput(projects) is correct for the new single-argument
signature, but the async fetch flow leaves the loading state stuck on error;
update the fetch/error handling around getProjects so setLoading(true) is
balanced by a finally that calls setLoading(false) (i.e., add a finally {
setLoading(false); } after the try/catch that surrounds getProjects and
setLoading calls) to ensure the UI always clears the loading state even when an
exception is thrown.

setData(modifiedProjects);
setRenderProjects(true);
resetFilters();
Expand Down
128 changes: 90 additions & 38 deletions apps/web/src/utils/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,89 @@ import { FilterProps, RepositoryProps } from "@opensox/shared/types";
const getDateFromPast = (days: number): string => {
const now = new Date();
const pastDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);

const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0");
const day = String(pastDate.getDate()).padStart(2, "0");

return `${year}-${month}-${day}`;
};

// Usage:
// console.log(getDateFromPast(7)); // get the date of 7 days ago from today
const categorize = (
value: number,
ranges: { [key: string]: { min?: string; max?: string } },
): string => {
for (const [label, range] of Object.entries(ranges)) {
const min = range.min ? parseInt(range.min) : 0;
const max = range.max ? parseInt(range.max) : Infinity;
if (value >= min && value <= max) return label;
}
return "Very low";
};

const computePopularity = (stars: number): string => {
const ranges: { [key: string]: { min?: string; max?: string } } = {
"Very low": { min: "10", max: "500" },
Low: { min: "501", max: "1000" },
Moderate: { min: "1001", max: "2000" },
High: { min: "2001", max: "5000" },
"Very high": { min: "5001" },
};
return categorize(stars, ranges);
};

const computeCompetition = (forks: number): string => {
const ranges: { [key: string]: { min?: string; max?: string } } = {
"Very low": { min: "0", max: "200" },
Low: { min: "201", max: "500" },
Moderate: { min: "501", max: "1000" },
High: { min: "1001", max: "2000" },
"Very high": { min: "2001" },
};
return categorize(forks, ranges);
};

const computeStage = (createdAt: string): string => {
const created = new Date(createdAt);
const now = new Date();
const daysSinceCreation = Math.floor(
(now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24),
);
if (daysSinceCreation <= 180) return "Very early";
if (daysSinceCreation <= 365) return "Early";
if (daysSinceCreation <= 913) return "Emerging";
return "Established";
};

const computeActivity = (pushedAt: string): string => {
const pushed = new Date(pushedAt);
const now = new Date();
const daysSincePush = Math.floor(
(now.getTime() - pushed.getTime()) / (1000 * 60 * 60 * 24),
);
if (daysSincePush <= 0) return "Highest";
if (daysSincePush <= 7) return "High";
if (daysSincePush <= 30) return "Normal";
return "Low";
};

// // USER INPUT EXAMPLE

// const userInput = {
// "Tech stack": "Python",
// Popularity: "Low",
// Competition: "High",
// Stage: "Very early",
// Activity: "Normal",
// };

// API INPUT EXAMPLE

// const apiInput = {
// language: "python",
// stars: { min: "100", max: "2000" },
// forks: { min: "50", max: "1000" },
// pushed: ">=2024-12-08",
// created: ">=2024-12-12",
// };

interface Range {
min?: string;
Expand Down Expand Up @@ -73,41 +146,21 @@ const userInputValues: UserFilterObjProps = {
"Very high": { min: "2001" },
},
Stage: {
"Very early": `>=${getDateFromPast(180)}`, // within 6 months
Early: `>=${getDateFromPast(365)}`, // within this year
Emerging: `>=${getDateFromPast(913)}`, // within this 2.5 years
Established: `>=${getDateFromPast(1825)}`, // within last 5 years
"Very early": `>=${getDateFromPast(180)}`,
Early: `>=${getDateFromPast(365)}`,
Emerging: `>=${getDateFromPast(913)}`,
Established: `>=${getDateFromPast(1825)}`,
},
Activity: {
Highest: `>=${getDateFromPast(0)}`, // within today
High: `>=${getDateFromPast(7)}`, // within this week
Normal: `>=${getDateFromPast(30)}`, // within this month
Low: `>=${getDateFromPast(365)}`, // withing this year
Highest: `>=${getDateFromPast(0)}`,
High: `>=${getDateFromPast(7)}`,
Normal: `>=${getDateFromPast(30)}`,
Low: `>=${getDateFromPast(365)}`,
},
};

// // USER INPUT EXAMPLE

// const userInput = {
// "Tech stack": "Python",
// Popularity: "Low",
// Competition: "High",
// Stage: "Very early",
// Activity: "Normal",
// };

// API INPUT EXAMPLE

// const apiInput = {
// language: "python",
// stars: { min: "100", max: "2000" },
// forks: { min: "50", max: "1000" },
// pushed: ">=2024-12-08",
// created: ">=2024-12-12",
// };

export const convertUserInputToApiInput = (
filter: UserInputFilterProps
filter: UserInputFilterProps,
): FilterProps => {
const data: Partial<FilterProps> = {};

Expand Down Expand Up @@ -139,7 +192,6 @@ export const convertUserInputToApiInput = (

export const convertApiOutputToUserOutput = (
response: RepositoryProps[],
filters: UserInputFilterProps
): DashboardProjectsProps[] => {
const data = response.map((item) => ({
id: item.id,
Expand All @@ -149,10 +201,10 @@ export const convertApiOutputToUserOutput = (
avatarUrl: item.owner.avatarUrl,
totalIssueCount: item.issues.totalCount,
primaryLanguage: item.primaryLanguage?.name || "Other",
popularity: filters.Popularity ? filters.Popularity : "-",
stage: filters.Stage ? filters.Stage : "-",
competition: filters.Competition ? filters.Competition : "-",
activity: filters.Activity ? filters.Activity : "-",
popularity: computePopularity(item.stargazerCount),
stage: computeStage(item.createdAt),
competition: computeCompetition(item.forkCount),
activity: computeActivity(item.pushedAt),
}));
return data;
};
4 changes: 4 additions & 0 deletions packages/shared/types/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export type RepositoryProps = {
primaryLanguage: {
name: string;
};
stargazerCount: number;
forkCount: number;
pushedAt: string;
createdAt: string;
};

export type GraphQLResponseProps = {
Expand Down