Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter feeds #427

Closed
wants to merge 18 commits into from
Closed
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
28 changes: 28 additions & 0 deletions app/api/leaderboard/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ReleasesResponse,
LeaderboardAPIResponse,
Release,
Repository,
} from "@/lib/types";
import { getContributors } from "@/lib/api";
import { env } from "@/env.mjs";
Expand Down Expand Up @@ -118,3 +119,30 @@ export default async function fetchGitHubReleases(
)
.slice(0, sliceLimit);
}

export async function fetchAllReposName() {
const result = await octokit.graphql.paginate(
`
query paginate($cursor: String, $organization: String!) {
organization(login: $organization) {
repositories(first: 10, after: $cursor, orderBy: { field: STARGAZERS, direction: DESC }) {
nodes {
name
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
{
organization: env.NEXT_PUBLIC_GITHUB_ORG,
},
);

return result.organization.repositories.nodes.map(
(r: { name: string }) => r.name,
) as string[];
}
71 changes: 55 additions & 16 deletions app/feed/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import LoadingText from "@/components/LoadingText";
import { IGitHubEvent } from "@/lib/gh_events";
import GitHubEvent from "@/components/gh_events/GitHubEvent";
import { env } from "@/env.mjs";
import octokit from "@/lib/octokit";
import { fetchAllReposName } from "../api/leaderboard/functions";
import GithubFeedFilter from "../../components/gh_events/GithubFeedFilter";
import GitHubEvent from "@/components/gh_events/GitHubEvent";
import { FilterOption } from "@/lib/types";

const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG;

export const revalidate = 600;

type Props = {
interface FeedPageProps {
searchParams: {
page?: string;
repository: string;
events: string;
};
};
}

export default async function FeedPage({ searchParams }: Props) {
const events = await octokit.paginate(
export default async function FeedPage({ searchParams }: FeedPageProps) {
const repositories = await fetchAllReposName();
const filterEvents: FilterOption[] = [
{ title: "repository", options: repositories },
{
title: "events",
options: [
"PullRequest ReviewCommentEvent",
"PullRequest ReviewEvent",
"Member Event",
"Issues Event",
"Issue CommentEvent",
"Pull RequestEvent",
"Push Event",
"Fork Event",
"Release Event",
],
},
] as const;
let allEvents = await octokit.paginate(
"GET /orgs/{org}/events",
{
org: GITHUB_ORG,
Expand All @@ -26,18 +47,36 @@ export default async function FeedPage({ searchParams }: Props) {
return data.filter(exludeBotEvents).filter(excludeBlacklistedEvents);
},
);
if (!Object.entries(events).length) {
if (!Object.entries(allEvents).length) {
return <LoadingText text="Fetching latest events" />;
}

if (searchParams.repository) {
allEvents = allEvents.filter((e) =>
e.repo.name.includes(
env.NEXT_PUBLIC_GITHUB_ORG + "/" + searchParams.repository,
),
);
}

if (searchParams.events) {
allEvents = allEvents.filter((e) => e.type === searchParams.events);
}

return (
<div className="relative mx-auto my-8 flow-root max-w-4xl p-4">
<h1 className="text-4xl text-primary-500 dark:text-white">Feed</h1>
<ul role="list" className="mb-20 mt-10 flex flex-col gap-4 space-y-4">
{events.map((e) => (
<GitHubEvent key={e.id} event={e} />
))}
</ul>
</div>
<>
<div className="flex-col">
<GithubFeedFilter filterEvents={filterEvents} />
<div className="relative flow-root w-full max-w-4xl p-4 lg:my-8">
<h1 className="text-4xl text-primary-500 dark:text-white">Feed</h1>
<ul role="list" className="mb-20 mt-10 flex flex-col gap-4 space-y-4">
{allEvents.map((e) => (
<GitHubEvent key={e.id} event={e} />
))}
</ul>
</div>
</div>
</>
);
}

Expand Down
8 changes: 8 additions & 0 deletions components/filters/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { BsSearch } from "react-icons/bs";

const Search = ({
value,
handleOnChange,
className = "",
defaultValue = "",
suggestions = [],
}: {
handleOnChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value?: string;
className?: string;
defaultValue?: string;
suggestions?: string[];
}) => {
const showSuggestions = suggestions.length > 0;

return (
<div className={"relative rounded-md shadow-sm " + className}>
<div className="pointer-events-none absolute top-3 flex items-center pl-3">
Expand All @@ -18,7 +24,9 @@ const Search = ({
type="text"
name="search"
id="search"
autoComplete="off"
onChange={handleOnChange}
value={value}
defaultValue={defaultValue}
className="block w-full rounded-md border border-secondary-600 bg-transparent p-2 pl-10 text-sm text-foreground dark:border-secondary-300"
placeholder="Start typing to search..."
Expand Down
106 changes: 106 additions & 0 deletions components/gh_events/GithubFeedFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"use client";
import { FilterOption } from "@/lib/types";
import Search from "../filters/Search";
import { useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";

const GithubFeed = (props: { filterEvents: FilterOption[] }) => {
const { filterEvents } = props;

const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const [searchValue, setSearchValue] = useState("");
const [suggestions, setSuggestions] = useState<string[]>([]);

const updateSearchParam = (key: string, value: string) => {
setSearchValue(value);
setSuggestions([]);
const params = new URLSearchParams(searchParams.toString());

!!value ? params.set(key, value) : params.delete(key);

router.push(pathname + "?" + params.toString());
};
const repositories = filterEvents.find(
(filterEvents) => filterEvents.title === "repository",
);

// Load suggestions of repsitories name based on user input
const autoFill = (value: string) => {
setSearchValue(value);
if (value.length === 0) {
setSuggestions([]);
return;
}
const filteredSuggestions: string[] | undefined =
repositories?.options?.filter((repo) =>
repo.toLowerCase().includes(value.toLowerCase()),
);
setSuggestions(filteredSuggestions ?? []);
};

return (
<div className="mt-4 flex flex-col items-center justify-around py-5">
<div className="flex w-full">
<div className="w-3/5">
<Search
value={searchValue}
defaultValue={"" ?? ""}
handleOnChange={(e) => autoFill(e.target.value)}
className="w-full grow"
/>
{suggestions && suggestions.length > 0 && (
<div className="absolute z-10 max-h-[50%] w-1/2 overflow-y-scroll rounded-md border-2 border-secondary-100 bg-background text-foreground shadow-lg">
<ul className="divide-y">
{suggestions?.map((opt, index) => (
<li
key={index}
onClick={(e) => {
updateSearchParam("repository", opt);
}}
className="cursor-pointer px-3 py-1 hover:bg-gray-500"
>
{opt}
</li>
))}
</ul>
</div>
)}
</div>
<div className="ml-4 flex items-center rounded-md border-2 border-secondary-500 p-1 md:ml-6">
<ul className="mx-auto">
{filterEvents.map((filter, index) => (
<li key={index} className="flex flex-col">
{filter.title === "events" && (
<select
className="cursor-pointer bg-background px-3 py-1 text-sm sm:p-1"
value={filter.selectedOption}
onChange={(e) => {
updateSearchParam(filter.title, e.target.value);
}}
>
<option className="cursor-pointer px-3 py-2" value="">
All
</option>
{filter.options.map((option, optionIndex) => (
<option
className="cursor-pointer px-3 py-2"
key={optionIndex}
value={option.replace(/\s+/g, "")}
>
{option}
</option>
))}
</select>
)}
</li>
))}
</ul>
</div>
</div>
</div>
);
};

export default GithubFeed;
7 changes: 6 additions & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ export type ContributorSocials = {
linkedin: string;
slack: string;
};

export type FilterOption = {
[x: string]: any;
title: string;
options: string[];
selectedOption?: string; // Add selectedOption property
};
export type PageProps = {
searchParams: {
search?: string;
Expand Down
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.