Skip to content

Commit

Permalink
Merge pull request #89 from brown-ccv/firebase-pagination
Browse files Browse the repository at this point in the history
Firebase pagination
  • Loading branch information
anna-murphy authored Jul 22, 2024
2 parents ca9c2b0 + 9aeb023 commit 74def50
Show file tree
Hide file tree
Showing 10 changed files with 886 additions and 53 deletions.
476 changes: 475 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"eject": "react-scripts eject",
"format": "prettier --write .",
"lint": "eslint --ext .ts,.js,.jsx,.tsx src types --fix",
"prepare": "husky install"
"prepare": "husky install",
"scripts:addQueryTokens": "tsx scripts/addQueryTokensToPubs.ts"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.5.1",
Expand Down Expand Up @@ -46,7 +47,8 @@
"firebase-tools": "^13.0.3",
"husky": "^8.0.3",
"lint-staged": "^15.2.0",
"prettier": "^3.2.4"
"prettier": "^3.2.4",
"tsx": "^4.16.2"
},
"eslintConfig": {
"extends": [
Expand Down
1 change: 1 addition & 0 deletions scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
serviceAccount.json
59 changes: 59 additions & 0 deletions scripts/addQueryTokensToPubs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { existsSync } from 'fs';

import admin from 'firebase-admin';
import { applicationDefault } from 'firebase-admin/app';
// import { Timestamp } from 'firebase-admin/firestore';

import { createPublicationTokens } from '../src/utils/firebase';

const SERVICE_ACCOUNT_PATH = './scripts/serviceAccount.json';

type DB = ReturnType<(typeof admin)['firestore']>;
type Pubs = Awaited<ReturnType<typeof getPubs>>;

function setup() {
if (existsSync(SERVICE_ACCOUNT_PATH)) {
process.env.GOOGLE_APPLICATION_CREDENTIALS = SERVICE_ACCOUNT_PATH;
console.log('Using Service Account Json');
} else {
console.log('Unable to find Service Account Json. Aborting...');
process.exit(-1);
}

admin.initializeApp({ credential: applicationDefault(), projectId: 'ccv-pubs' });
const db = admin.firestore();
return { admin, db };
}

async function getPubs(db: DB) {
const moduleDocs = await db.collection('publications').get();
if (moduleDocs.empty) {
console.error('No publications found.');
return [];
}
return moduleDocs.docs;
}

async function updatePubsWithTokens(db: DB, pubs: Pubs) {
await Promise.all(
pubs.map((pub) => {
const pubData = pub.data() as { title: string; author: string; updatedAt: number };
const newData = {
...pubData,
//updatedAt: Timestamp.fromDate(new Date(pubData.updatedAt)),
tokens: createPublicationTokens(pubData),
};
return db.doc(`publications/${pub.id}`).set(newData);
})
);
}

async function main() {
const { db } = setup();
const pubs = await getPubs(db);
console.log('fetched', pubs.length, 'publications');
await updatePubsWithTokens(db, pubs);
console.log('Done.');
}

main();
30 changes: 16 additions & 14 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';

import { Navbar } from './components/react-ccv-components/Navbar.tsx';
import Footer from './components/react-ccv-components/Footer';

import { ContentPage } from './components/ContentPage';
import { useAuthStateChanged, usePublicationsCollection } from './utils/firebase.ts';
import { PublicationsProvider } from './utils/PublicationsContext.tsx';
import { useAuthStateChanged } from './utils/firebase.ts';

export function App() {
useAuthStateChanged();
usePublicationsCollection();

return (
<div aria-live="polite">
<Navbar />
<BrowserRouter>
<main className="main">
<Routes>
<Route exact path="/" element={<ContentPage />} />
</Routes>
</main>
</BrowserRouter>
<Footer />
</div>
<PublicationsProvider>
<div aria-live="polite">
<Navbar />
<BrowserRouter>
<main className="main">
<Routes>
<Route exact path="/" element={<ContentPage />} />
</Routes>
</main>
</BrowserRouter>
<Footer />
</div>
</PublicationsProvider>
);
}
27 changes: 8 additions & 19 deletions src/components/ContentPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBook } from '@fortawesome/free-solid-svg-icons';
import { useSelector } from 'react-redux';
import { selectPublications, selectUser } from '../store/slice/appState';
import { selectUser } from '../store/slice/appState';
import { PublicationsTable } from './PublicationsTable.tsx';
import Spinner from './Spinner';
import { AddPublicationModal } from './AddPublicationModal.tsx';
import { YearChart } from './YearChart.tsx';

export function ContentPage() {
const publications = useSelector(selectPublications);
const user = useSelector(selectUser);

return (
Expand All @@ -25,22 +22,14 @@ export function ContentPage() {
<h1 className="mx-2">Publications</h1>
</div>

<Spinner loading={publications.length === 0} className="spinner" size={100} />
<PublicationsTable />

{publications.length !== 0 && (
<>
<PublicationsTable />

{/* TODO: Word Cloud #58 */}
<h2 className="title pt-4 m-4 is-2 text-center">
What are these publications all about?
</h2>
{/*<div className="viz d-flex justify-content-center pt-5">*/}
{/* <WordCloud />*/}
<YearChart />
{/*</div>*/}
</>
)}
{/* TODO: Word Cloud #58 */}
{/*<h2 className="title pt-4 m-4 is-2 text-center">What are these publications all about?</h2>*/}
{/*<div className="viz d-flex justify-content-center pt-5">*/}
{/* <WordCloud />*/}
{/*<YearChart />*/}
{/*</div>*/}
</div>
);
}
74 changes: 59 additions & 15 deletions src/components/PublicationsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { useSelector } from 'react-redux';

import {
ColumnFiltersState,
Expand All @@ -9,10 +8,8 @@ import {
getFacetedMinMaxValues,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
SortingState,
useReactTable,
} from '@tanstack/react-table';

Expand All @@ -26,17 +23,59 @@ import Form from 'react-bootstrap/Form';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowDownWideShort, faArrowUpShortWide } from '@fortawesome/free-solid-svg-icons';

import { selectPublications } from '../store/slice/appState';
import { Publication } from '../../types';
import { Publication, PublicationOrderFields } from '../../types';
import { usePublicationContext } from '../utils/PublicationsContext.tsx';
import { cleanTokenString } from '../utils/firebase.ts';
import { ColumnFilter } from './ColumnFilter.tsx';

export function PublicationsTable() {
const publications = useSelector(selectPublications);
const {
pubs,
count,
orderBy,
pagination,
setters: {
setAuthorFilters,
setTitleFilters,
setYearMax,
setYearMin,
setPagination,
setOrderBy,
},
} = usePublicationContext();
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 5,
});
const [sorting, setSorting] = React.useState<SortingState>([
{ id: orderBy.field, desc: orderBy.dir === 'desc' },
]);

React.useEffect(() => {
const sortingValue = sorting[0];
if (sortingValue !== undefined) {
setOrderBy({
field: sortingValue.id as PublicationOrderFields,
dir: sortingValue.desc ? 'desc' : 'asc',
});
} else {
setSorting([{ id: orderBy.field, desc: orderBy.dir !== 'desc' }]);
}
}, [sorting, orderBy, setSorting, setOrderBy]);

React.useEffect(() => {
const authorFilters = columnFilters.filter((filter) => filter.id === 'author').pop();
const titleFilters = columnFilters.filter((filter) => filter.id === 'title').pop();
const yearFilters = columnFilters.filter((filter) => filter.id === 'year').pop();
if (authorFilters !== undefined) {
setAuthorFilters(cleanTokenString(authorFilters.value as string));
} else setAuthorFilters([]);
if (titleFilters !== undefined) {
setTitleFilters(cleanTokenString(titleFilters.value as string));
} else setTitleFilters([]);
if (yearFilters !== undefined) {
const [yearMin, yearMax] = yearFilters.value as [number | undefined, number | undefined];
if (yearMin !== undefined) setYearMin(Number(yearMin));
if (yearMax !== undefined) setYearMax(Number(yearMax));
}
}, [sorting, columnFilters, setAuthorFilters, setTitleFilters, setYearMax, setYearMin]);

const columnHelper = createColumnHelper<Publication>();

Expand All @@ -61,18 +100,23 @@ export function PublicationsTable() {
];

const table = useReactTable({
data: publications,
data: pubs,
columns,
state: {
columnFilters,
pagination,
sorting,
},
columnResizeMode: 'onChange',
onColumnFiltersChange: setColumnFilters,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
manualSorting: true,
onSortingChange: setSorting,
manualFiltering: true,
manualPagination: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onPaginationChange: setPagination as any,
pageCount: Math.ceil(count / pagination.pageSize),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
Expand Down
Loading

0 comments on commit 74def50

Please sign in to comment.