Skip to content

Commit 09238bc

Browse files
authored
Merge pull request #193 from wpengine/fixes
(fix): misc small issues and smart search fixes
2 parents 7ec59e2 + 202a606 commit 09238bc

File tree

9 files changed

+124
-97
lines changed

9 files changed

+124
-97
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const config = [
4343
...unicorn.configs["flat/recommended"].rules, // neon disables a lot of unicorn rules so this reenables defaults
4444
"react/no-danger": "warn",
4545
"react/jsx-sort-props": "off",
46+
"@stylistic/jsx/jsx-sort-props": "off",
4647
"unicorn/prevent-abbreviations": [
4748
"error",
4849
{

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"downshift": "^9.0.8",
3232
"graphql": "^16.8.1",
3333
"html-to-text": "^9.0.5",
34+
"http-status-codes": "^2.3.0",
3435
"lodash.debounce": "^4.0.8",
3536
"next": "^15.0.1",
3637
"react": "^18.3.1",

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/gtag.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// eslint-disable-next-line no-restricted-globals, n/prefer-global/process
1+
import process from "process"; // eslint-disable-line unicorn/prefer-node-protocol
2+
23
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY;
34

45
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
56
export const logPageview = (url) => {
6-
window.gtag("config", GA_TRACKING_ID, {
7+
globalThis.gtag("config", GA_TRACKING_ID, {
78
page_path: url,
89
});
910
};

src/lib/smart-search-plugin.mjs

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { hash } from "node:crypto";
12
import fs from "node:fs/promises";
23
import path from "node:path";
34
import { cwd } from "node:process";
45
import { htmlToText } from "html-to-text";
56

6-
function smartSearchPlugin({ endpoint, accessToken }) {
7-
let isPluginExecuted = false;
7+
let isPluginExecuted = false;
88

9+
function smartSearchPlugin({ endpoint, accessToken }) {
910
return {
1011
apply: (compiler) => {
1112
compiler.hooks.done.tapPromise("SmartSearchPlugin", async () => {
@@ -25,8 +26,8 @@ function smartSearchPlugin({ endpoint, accessToken }) {
2526

2627
console.log("Docs Pages collected for indexing:", pages.length);
2728

28-
await deleteExistingDocs(endpoint, accessToken);
29-
await sendPagesToEndpoint(pages, endpoint, accessToken);
29+
await deleteOldDocs({ endpoint, accessToken }, pages);
30+
await sendPagesToEndpoint({ endpoint, accessToken }, pages);
3031
} catch (error) {
3132
console.error("Error in smartSearchPlugin:", error);
3233
}
@@ -54,12 +55,9 @@ async function collectPages(directory) {
5455

5556
let metadata = {};
5657

57-
if (
58-
metadataMatch &&
59-
metadataMatch.groups &&
60-
metadataMatch.groups.metadata
61-
) {
58+
if (metadataMatch?.groups?.metadata) {
6259
try {
60+
// eslint-disable-next-line no-eval
6361
metadata = eval(`(${metadataMatch.groups.metadata})`);
6462
} catch (error) {
6563
console.error("Error parsing metadata:", error);
@@ -74,9 +72,7 @@ async function collectPages(directory) {
7472

7573
const cleanedPath = cleanPath(entryPath);
7674

77-
const id = `mdx:${cleanedPath}`;
78-
79-
console.log(`Indexing document with ID: ${id}, path: ${cleanedPath}`);
75+
const id = hash("sha-1", `mdx:${cleanedPath}`);
8076

8177
pages.push({
8278
id,
@@ -105,16 +101,28 @@ function cleanPath(filePath) {
105101
);
106102
}
107103

108-
async function deleteExistingDocs(endpoint, accessToken) {
109-
const queryDocuments = `
110-
query FindDocumentsToDelete($query: String!) {
111-
find(query: $query) {
112-
documents {
113-
id
114-
}
104+
const queryDocuments = `
105+
query FindIndexedMdxDocs($query: String!) {
106+
find(query: $query) {
107+
documents {
108+
id
115109
}
116-
}
117-
`;
110+
}
111+
}
112+
`;
113+
114+
const deleteMutation = `
115+
mutation DeleteDocument($id: ID!) {
116+
delete(id: $id) {
117+
code
118+
message
119+
success
120+
}
121+
}
122+
`;
123+
124+
async function deleteOldDocs({ endpoint, accessToken }, pages) {
125+
const currentMdxDocuments = new Set(pages.map((page) => page.id));
118126

119127
const variablesForQuery = {
120128
query: 'content_type:"mdx_doc"',
@@ -136,30 +144,25 @@ async function deleteExistingDocs(endpoint, accessToken) {
136144
const result = await response.json();
137145

138146
if (result.errors) {
139-
console.error("Error fetching documents to delete:", result.errors);
147+
console.error("Error fetching existing documents:", result.errors);
140148
return;
141149
}
142150

143-
const documentsToDelete = result.data.find.documents;
151+
const existingIndexedDocuments = new Set(
152+
result.data.find.documents.map((doc) => doc.id),
153+
);
154+
155+
const documentsToDelete =
156+
existingIndexedDocuments.difference(currentMdxDocuments);
144157

145-
if (!documentsToDelete || documentsToDelete.length === 0) {
158+
if (documentsToDelete?.size === 0) {
146159
console.log("No documents to delete.");
147160
return;
148161
}
149162

150-
for (const doc of documentsToDelete) {
151-
const deleteMutation = `
152-
mutation DeleteDocument($id: ID!) {
153-
delete(id: $id) {
154-
code
155-
message
156-
success
157-
}
158-
}
159-
`;
160-
163+
for (const doc of documentsToDelete.values()) {
161164
const variablesForDelete = {
162-
id: doc.id,
165+
id: doc,
163166
};
164167

165168
try {
@@ -179,14 +182,11 @@ async function deleteExistingDocs(endpoint, accessToken) {
179182

180183
if (deleteResult.errors) {
181184
console.error(
182-
`Error deleting document ID ${doc.id}:`,
185+
`Error deleting document ID ${doc}:`,
183186
deleteResult.errors,
184187
);
185188
} else {
186-
console.log(
187-
`Deleted document ID ${doc.id}:`,
188-
deleteResult.data.delete,
189-
);
189+
console.log(`Deleted document ID ${doc}:`, deleteResult.data.delete);
190190
}
191191
} catch (error) {
192192
console.error(`Network error deleting document ID ${doc.id}:`, error);
@@ -198,18 +198,17 @@ async function deleteExistingDocs(endpoint, accessToken) {
198198
}
199199

200200
const bulkIndexQuery = `
201-
mutation BulkIndex($documents: [DocumentInput!]!) {
202-
bulkIndex(input: { documents: $documents }) {
201+
mutation BulkIndex($input: BulkIndexInput!) {
202+
bulkIndex(input: $input) {
203203
code
204204
documents {
205205
id
206-
data
207206
}
208207
}
209208
}
210209
`;
211210

212-
async function sendPagesToEndpoint(pages, endpoint, accessToken) {
211+
async function sendPagesToEndpoint({ endpoint, accessToken }, pages) {
213212
if (pages.length === 0) {
214213
console.warn("No documents found for indexing.");
215214
return;
@@ -220,7 +219,7 @@ async function sendPagesToEndpoint(pages, endpoint, accessToken) {
220219
data: page.data,
221220
}));
222221

223-
const variables = { documents };
222+
const variables = { input: { documents } };
224223

225224
try {
226225
const response = await fetch(endpoint, {

src/pages/_app.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import { WordPressBlocksProvider } from "@faustwp/blocks";
22
import { FaustProvider } from "@faustwp/core";
33
import { useRouter } from "next/router";
44
import { useEffect } from "react";
5+
import Layout from "@/components/layout";
6+
import { logPageview } from "@/lib/gtag.js";
7+
import blocks from "@/wp-blocks";
58
import "../../faust.config";
69
import "./global.css";
7-
import Layout from "@/components/layout";
810
import "@faustwp/core/dist/css/toolbar.css";
9-
import * as gtag from "@/lib/gtag";
10-
import blocks from "@/wp-blocks";
1111

1212
export default function MyApp({ Component, pageProps }) {
1313
const router = useRouter();
1414

1515
// Record a Google Analytics pageview on route change
1616
useEffect(() => {
17-
const handleRouteChange = (url) => gtag.logPageview(url);
17+
const handleRouteChange = (url) => logPageview(url);
1818

1919
router.events.on("routeChangeComplete", handleRouteChange);
2020
return () => {

src/pages/api/search.js

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import process from "node:process";
2+
import { ReasonPhrases, StatusCodes } from "http-status-codes";
23

34
function cleanPath(filePath) {
45
return (
@@ -15,8 +16,16 @@ export default async function handler(req, res) {
1516
const accessToken = process.env.NEXT_SEARCH_ACCESS_TOKEN;
1617
const { query } = req.query;
1718

19+
if (req.method !== "GET") {
20+
return res
21+
.status(StatusCodes.METHOD_NOT_ALLOWED)
22+
.json({ error: ReasonPhrases.METHOD_NOT_ALLOWED });
23+
}
24+
1825
if (!query) {
19-
return res.status(400).json({ error: "Search query is required." });
26+
return res
27+
.status(StatusCodes.BAD_REQUEST)
28+
.json({ error: "Search query is required." });
2029
}
2130

2231
const graphqlQuery = `
@@ -44,56 +53,65 @@ export default async function handler(req, res) {
4453
}),
4554
});
4655

56+
if (!response.ok) {
57+
return res
58+
.status(StatusCodes.SERVICE_UNAVAILABLE)
59+
.json({ error: ReasonPhrases.SERVICE_UNAVAILABLE });
60+
}
61+
4762
const result = await response.json();
4863

4964
if (result.errors) {
50-
return res.status(500).json({ errors: result.errors });
65+
return res
66+
.status(StatusCodes.INTERNAL_SERVER_ERROR)
67+
.json({ errors: result.errors });
5168
}
5269

53-
const formattedResults = result.data.find.documents
54-
.map((content) => {
55-
const contentType =
56-
content.data.content_type || content.data.post_type || "mdx_doc";
57-
58-
if (contentType === "mdx_doc" && content.data.title) {
59-
const path = content.data.path ? cleanPath(content.data.path) : "/";
60-
return {
61-
id: content.id,
62-
title: content.data.title,
63-
path,
64-
type: "mdx_doc",
65-
};
66-
}
67-
68-
if (
69-
(contentType === "wp_post" || contentType === "post") &&
70-
content.data.post_title &&
71-
content.data.post_name
72-
) {
73-
return {
74-
id: content.id,
75-
title: content.data.post_title,
76-
path: `/blog/${content.data.post_name}`,
77-
type: "post",
78-
};
79-
}
80-
81-
return null;
82-
})
83-
.filter((item) => item !== null);
84-
8570
const seenIds = new Set();
86-
const uniqueResults = formattedResults.filter((item) => {
71+
const formattedResults = [];
72+
73+
for (const content of result.data.find.documents) {
74+
const contentType =
75+
content.data.content_type || content.data.post_type || "mdx_doc";
76+
77+
let item = {};
78+
79+
if (contentType === "mdx_doc" && content.data.title) {
80+
const path = content.data.path ? cleanPath(content.data.path) : "/";
81+
item = {
82+
id: content.id,
83+
title: content.data.title,
84+
path,
85+
type: "mdx_doc",
86+
};
87+
}
88+
89+
if (
90+
(contentType === "wp_post" || contentType === "post") &&
91+
content.data.post_title &&
92+
content.data.post_name
93+
) {
94+
item = {
95+
id: content.id,
96+
title: content.data.post_title,
97+
path: `/blog/${content.data.post_name}`,
98+
type: "post",
99+
};
100+
}
101+
87102
if (seenIds.has(item.id)) {
88-
return false;
103+
continue;
89104
}
105+
90106
seenIds.add(item.id);
91-
return true;
92-
});
107+
formattedResults.push(item);
108+
}
93109

94-
return res.status(200).json(uniqueResults);
110+
return res.status(StatusCodes.OK).json(formattedResults);
95111
} catch (error) {
96112
console.error("Error fetching search data:", error);
97-
return res.status(500).json({ error: "Internal server error" });
113+
return res
114+
.status(StatusCodes.Inter)
115+
.json({ error: ReasonPhrases.INTERNAL_SERVER_ERROR });
98116
}
99117
}

0 commit comments

Comments
 (0)