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

Source related resource content from Swiftype #594

Merged
merged 33 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1329519
chore: install node-fetch
jerelmiller Aug 12, 2020
0568e5c
feat: add local plugin that creates the json file that will be writte…
jerelmiller Aug 12, 2020
10b2f78
chore: create the file empty for now
jerelmiller Aug 12, 2020
da64b13
feat: add ability to filter which pages are fetched
jerelmiller Aug 12, 2020
bb5c574
chore: write empty object to the data file by default
jerelmiller Aug 12, 2020
27c6fe9
chore: use file option instead of path
jerelmiller Aug 13, 2020
27c1481
feat: add general params that will be used to fetch from swiftype
jerelmiller Aug 13, 2020
7a13ad7
chore: rename getPath to getParams that will be used to populate swif…
jerelmiller Aug 13, 2020
9e3b486
feat: add tags to search query and write data to file in plugin
jerelmiller Aug 13, 2020
8052577
refactor: move url helpers to utils file
jerelmiller Aug 13, 2020
65b83c6
refactor: move path back to getPath function
jerelmiller Aug 13, 2020
8232332
feat: fetch results from swiftype for the node and save the data
jerelmiller Aug 13, 2020
7a4c0e5
fix: filter out certain document types to match the in product experi…
jerelmiller Aug 13, 2020
6d77c70
feat: add config to enable swiftype builds using env variable
jerelmiller Aug 13, 2020
6d7f1f4
chore: default getParams function
jerelmiller Aug 13, 2020
a90318d
refactor: move search logic out to own search function
jerelmiller Aug 13, 2020
04b1828
refactor: use plain url strings intead of URL instance
jerelmiller Aug 13, 2020
674e1c7
feat: filter out redirects and already defined urls for a page
jerelmiller Aug 13, 2020
3f56794
chore: read/write to the json file in onCreateNode
jerelmiller Aug 13, 2020
53053ef
chore: mock related pages by using json param data for now
jerelmiller Aug 13, 2020
d36afbc
feat: add graphql schema item and dummy resolver for related resources
jerelmiller Aug 13, 2020
6685062
chore: better mock data
jerelmiller Aug 13, 2020
753cd77
fix: fix race condition causing incorrect data to be written to file
jerelmiller Aug 13, 2020
5c8cc98
refactor: slightly more efficient means of getting related resources …
jerelmiller Aug 13, 2020
f4c8fd1
feat: implement resolver for related resources
jerelmiller Aug 13, 2020
3efef9d
feat: add actual related resource data
jerelmiller Aug 13, 2020
0ad50b9
feat: properly resolve related resources for a node by linking the pa…
jerelmiller Aug 13, 2020
689b3d5
chore: use getNodesByIds instead of getAllNodes
jerelmiller Aug 13, 2020
89abf63
chore: exclude discuss and blog results
jerelmiller Aug 13, 2020
489d004
chore: rename enabled to refetch
jerelmiller Aug 13, 2020
cc22434
feat: add limit argument to relatedResources
jerelmiller Aug 13, 2020
fd536e6
chore: rename pageLimit to limit
jerelmiller Aug 13, 2020
4ce7c43
style: use arrow function instead
jerelmiller Aug 13, 2020
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
47 changes: 47 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const quote = (str) => `"${str}"`;

module.exports = {
siteMetadata: {
title: 'New Relic Developers',
Expand Down Expand Up @@ -42,6 +44,51 @@ module.exports = {
},
},
},
{
resolve: 'gatsby-source-swiftype',
options: {
file: `${__dirname}/src/data/related-pages.json`,
refetch: Boolean(process.env.BUILD_RELATED_CONTENT),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this for caching reasons?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. By default, the data is resolved from the src/data/related-pages.json file. When we need to refresh that data and make new requests to Swiftype, setting the BUILD_RELATED_CONTENT environment variable will instruct the plugin to go make new requests to Swiftype and save them in the JSON file.

engineKey: 'Ad9HfGjDw4GRkcmJjUut',
limit: 5,
getPath: ({ node }) => node.frontmatter.path,
getParams: ({ node }) => {
const {
tags,
title,
redirects = [],
resources = [],
} = node.frontmatter;

const filteredUrls = resources
.map((resource) => resource.url)
.concat(redirects);

return {
q: tags ? tags.map(quote).join(' OR ') : title,
search_fields: {
page: ['tags^10', 'body^5', 'title^1.5', '*'],
},
filters: {
page: {
type: ['!blog', '!forum'],
url: filteredUrls.map((url) =>
url.startsWith('/')
? `!https://developer.newrelic.com${url}`
: `!${url}`
),
document_type: [
'!views_page_menu',
'!term_page_api_menu',
'!term_page_landing_page',
],
},
},
};
},
filterNode: ({ node }) => node.frontmatter.template === 'GuideTemplate',
},
},
'gatsby-plugin-sass',
{
resolve: 'gatsby-plugin-manifest',
Expand Down
13 changes: 10 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"gatsby-transformer-remark": "^2.8.23",
"gatsby-transformer-sharp": "^2.5.10",
"js-cookie": "^2.2.1",
"node-fetch": "^2.6.0",
"node-sass": "^4.14.1",
"prism-react-renderer": "^1.1.1",
"prismjs": "^1.21.0",
Expand Down Expand Up @@ -71,6 +72,7 @@
"build": "gatsby build",
"build:production": "GATSBY_NEWRELIC_ENV=production gatsby build",
"build:staging": "GATSBY_NEWRELIC_ENV=staging gatsby build",
"build:related-content": "BUILD_RELATED_CONTENT=true npm run build:production",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,json,md}\"",
"start": "npm run develop",
Expand Down
93 changes: 93 additions & 0 deletions plugins/gatsby-source-swiftype/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const fs = require('fs');
const createRelatedResourceNode = require('./src/createRelatedResourceNode');
const getRelatedResources = require('./src/getRelatedResources');

const writeableData = {};

exports.onPreBootstrap = (_, pluginOptions) => {
const { file } = pluginOptions;

if (!fs.existsSync(file)) {
fs.writeFileSync(file, '{}');
}
};

exports.onCreateNode = async (
{ actions, node, getNodesByType, createNodeId, createContentDigest },
pluginOptions
) => {
const { createNode, createParentChildLink } = actions;
const { filterNode = () => false, getPath } = pluginOptions;

if (node.internal.type !== 'Mdx' || !filterNode({ node })) {
return;
}

const [
{
siteMetadata: { siteUrl },
},
] = getNodesByType('Site');

const pathname = getPath({ node });
const resources = await getRelatedResources({ node, siteUrl }, pluginOptions);

writeableData[pathname] = resources;

resources.forEach((resource) => {
const child = createRelatedResourceNode({
parent: node.id,
resource,
createContentDigest,
createNode,
createNodeId,
});

createParentChildLink({ parent: node, child: child });
});
};

exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;

const typeDefs = `
type RelatedResource implements Node {
id: ID!
title: String!
url: String!
}
`;

createTypes(typeDefs);
};

exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Mdx: {
relatedResources: {
args: {
limit: {
type: 'Int',
defaultValue: 5,
},
},
type: ['RelatedResource!'],
resolve: (source, args, context) => {
const { limit } = args;

return context.nodeModel
.getNodesByIds({ ids: source.children })
.slice(0, Math.max(limit, 0));
},
},
},
});
};

exports.onPostBootstrap = (_, pluginOptions) => {
const { refetch, file } = pluginOptions;

if (refetch) {
fs.writeFileSync(file, JSON.stringify(writeableData, null, 2));
}
};
1 change: 1 addition & 0 deletions plugins/gatsby-source-swiftype/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
25 changes: 25 additions & 0 deletions plugins/gatsby-source-swiftype/src/createRelatedResourceNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = ({
createNode,
createNodeId,
createContentDigest,
resource,
parent,
}) => {
const node = {
id: createNodeId(`RelatedResource-${resource.url}`),
title: resource.title,
url: resource.url,
parent,
children: [],
plugin: 'gatsby-source-swiftype',
internal: {
type: 'RelatedResource',
content: JSON.stringify(resource),
contentDigest: createContentDigest(resource),
},
};

createNode(node);

return node;
};
26 changes: 26 additions & 0 deletions plugins/gatsby-source-swiftype/src/getRelatedResources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const fs = require('fs');
const search = require('./search');

module.exports = async ({ node, siteUrl }, pluginOptions) => {
const {
refetch,
engineKey,
limit,
file,
getParams = () => ({}),
getPath,
} = pluginOptions;

const pathname = getPath({ node });

if (refetch) {
return search(siteUrl + pathname, getParams({ node }), {
engineKey,
limit,
});
}

const data = JSON.parse(fs.readFileSync(file));

return data[pathname] || [];
};
47 changes: 47 additions & 0 deletions plugins/gatsby-source-swiftype/src/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fetch = require('node-fetch');
const { appendTrailingSlash, stripTrailingSlash } = require('./utils/url');

const normalizeUrl = (url) => {
const prefix = url.startsWith('!') ? '!' : '';
const plainUrl = url.replace(/^!/, '');

return [
prefix + appendTrailingSlash(plainUrl),
prefix + stripTrailingSlash(plainUrl),
];
};

const uniq = (arr) => [...new Set(arr)];

module.exports = async (url, params = {}, { engineKey, limit }) => {
const { page: pageFilters = {} } = params.filters || {};

const res = await fetch(
'https://search-api.swiftype.com/api/v1/public/engines/search.json',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...params,
engine_key: engineKey,
per_page: limit,
filters: {
...params.filters,
page: {
...pageFilters,
url: uniq([
...normalizeUrl(`!${url}`),
...(pageFilters.url || []).flatMap(normalizeUrl),
]),
},
},
}),
}
);

const { records } = await res.json();

return records.page;
};
21 changes: 21 additions & 0 deletions plugins/gatsby-source-swiftype/src/utils/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const hasQueryParams = (urlString) => {
const url = new URL(urlString);

return Boolean(url.search);
};

exports.appendTrailingSlash = (url) => {
if (hasQueryParams(url)) {
return url;
}

return url.endsWith('/') ? url : `${url}/`;
};

exports.stripTrailingSlash = (url) => {
if (hasQueryParams(url)) {
return url;
}

return url.endsWith('/') ? url.replace(/\/$/, '') : url;
};
Loading