Skip to content

Commit 1e98833

Browse files
authored
Generate graph gist content (#8)
1 parent 6ccaf36 commit 1e98833

17 files changed

+1866
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules/
2+
build/

lib/generate-site.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,34 @@ const publishSite = require('@antora/site-publisher')
1313
const { resolveAsciiDocConfig } = require('@antora/asciidoc-loader')
1414
const knowledgeBase = require('./knowledge-base')
1515
const training = require('./training')
16+
const {
17+
addGraphGistPages,
18+
addGraphGistCategoryPages,
19+
addGraphGistIndexPage,
20+
generateJupyterNotebookAttachments,
21+
assignPageAttributes,
22+
} = require('./graphgists')
23+
const { getLiveGraphGists } = require('./graphgists/graphql-api')
1624

1725
async function generateSite (args, env) {
1826
const playbook = buildPlaybook(args, env)
1927
const asciidocConfig = resolveAsciiDocConfig(playbook)
20-
const [contentCatalog, uiCatalog] = await Promise.all([
28+
const siteComponent = asciidocConfig.attributes['site-component'] || ''
29+
const [contentCatalog, uiCatalog, graphGists] = await Promise.all([
2130
aggregateContent(playbook).then((contentAggregate) => classifyContent(playbook, contentAggregate, asciidocConfig)),
2231
loadUi(playbook),
32+
siteComponent === 'graphgists' ? getLiveGraphGists() : Promise.resolve({}),
2333
])
34+
if (siteComponent === 'graphgists') {
35+
addGraphGistPages(graphGists, contentCatalog, asciidocConfig)
36+
}
2437
const pages = convertDocuments(contentCatalog, asciidocConfig)
38+
if (siteComponent === 'graphgists') {
39+
generateJupyterNotebookAttachments(graphGists, contentCatalog)
40+
addGraphGistCategoryPages(graphGists, pages, contentCatalog, asciidocConfig)
41+
assignPageAttributes(graphGists, contentCatalog, asciidocConfig)
42+
addGraphGistIndexPage(graphGists, pages, contentCatalog, asciidocConfig)
43+
}
2544
knowledgeBase.generateKnowledgeBasePageDescription(pages)
2645
knowledgeBase.addCategoryPages(pages, contentCatalog, asciidocConfig)
2746
knowledgeBase.addTagPages(pages, contentCatalog, asciidocConfig)

lib/graphgists.js

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
'use strict'
2+
3+
const { convert: convertJupyterNotebook } = require('./graphgists/jupyter-converter')
4+
const { preprocessing: asciidocPreprocessing } = require('./graphgists/asciidoc-preprocessing')
5+
6+
const componentName = 'graphgists'
7+
const componentVersion = 'master'
8+
const componentModule = 'ROOT'
9+
10+
// Jupyter Notebook
11+
12+
function generateJupyterNotebookAttachments(graphGists, contentCatalog) {
13+
for (const graphGist of graphGists) {
14+
const notebookContent = convertJupyterNotebook(graphGist.asciidoc)
15+
contentCatalog.addFile(createJupyerNotebookAttachment(notebookContent, graphGist))
16+
}
17+
}
18+
19+
function createJupyerNotebookAttachment(notebookContent, graphGist) {
20+
return {
21+
contents: Buffer.from(notebookContent),
22+
src: {
23+
component: componentName,
24+
version: componentVersion,
25+
module: componentModule,
26+
family: 'attachment',
27+
relative: `${graphGist.slug}.ipynb`,
28+
stem: graphGist.slug,
29+
mediaType: 'text/asciidoc'
30+
}
31+
}
32+
}
33+
34+
// Category pages
35+
36+
function addGraphGistCategoryPages(graphGists, pages, contentCatalog, asciidocConfig) {
37+
const categoryPagesMap = new Map()
38+
const categories = getCategories(graphGists)
39+
for (const category of categories) {
40+
const categoryPage = contentCatalog.addFile(createGraphGistCategoryPage(category, asciidocConfig))
41+
categoryPagesMap.set(category.slug, categoryPage)
42+
}
43+
const graphGistsMap = new Map()
44+
for (const graphGist of graphGists) {
45+
graphGistsMap.set(graphGist.slug, graphGist)
46+
}
47+
let pagesPerCategory = {}
48+
for (const page of pages) {
49+
if (page.src.component === componentName && graphGistsMap.has(page.src.stem)) {
50+
const graphGist = graphGistsMap.get(page.src.stem)
51+
const asciidocAttributes = {
52+
...page.asciidoc.attributes,
53+
'page-industries': graphGist.industries.map((industry) => {
54+
const categoryPage = categoryPagesMap.get(industry.slug)
55+
return { ...industry, pub: categoryPage.pub }
56+
}),
57+
'page-usecases': graphGist.use_cases.map((useCase) => {
58+
const categoryPage = categoryPagesMap.get(useCase.slug)
59+
return { ...useCase, pub: categoryPage.pub }
60+
}),
61+
}
62+
page.asciidoc.attributes = asciidocAttributes
63+
const categories = new Set()
64+
if (graphGist.industries && graphGist.industries.length > 0) {
65+
for (const industry of graphGist.industries) {
66+
categories.add(industry.slug)
67+
}
68+
}
69+
if (graphGist.use_cases && graphGist.use_cases.length > 0) {
70+
for (const useCase of graphGist.use_cases) {
71+
categories.add(useCase.slug)
72+
}
73+
}
74+
for (const category of categories) {
75+
let pages
76+
if (category in pagesPerCategory) {
77+
pages = pagesPerCategory[category]
78+
} else {
79+
pages = []
80+
pagesPerCategory[category] = pages
81+
}
82+
pages.push(page)
83+
}
84+
}
85+
}
86+
const allCategories = Array.from(generateCategoryPagesMap(graphGists, contentCatalog).values())
87+
const allIndustries = allCategories.filter((value) => value.type === 'industry')
88+
const allUseCases = allCategories.filter((value) => value.type === 'use-case')
89+
for (const categoryPage of categoryPagesMap.values()) {
90+
const asciidocAttributes = {
91+
...categoryPage.asciidoc.attributes,
92+
'page-graphgist-pages': pagesPerCategory[categoryPage.src.stem],
93+
'page-industries': allIndustries,
94+
'page-use-cases': allUseCases
95+
}
96+
categoryPage.asciidoc.attributes = asciidocAttributes
97+
pages.push(categoryPage)
98+
}
99+
}
100+
101+
function createGraphGistCategoryPage(category, asciidocConfig) {
102+
const asciidocAttributes = {
103+
...asciidocConfig.attributes,
104+
'page-layout': 'graphgist-category',
105+
'page-category': category.name,
106+
'page-slug': category.slug,
107+
'page-breadcrumb': category.name,
108+
'page-category-type': category.type
109+
}
110+
return {
111+
title: `Category ${category.name}`,
112+
asciidoc: {
113+
attributes: asciidocAttributes
114+
},
115+
src: {
116+
component: componentName,
117+
version: componentVersion,
118+
module: componentModule,
119+
family: 'page',
120+
relative: `categories/${category.slug}.adoc`,
121+
stem: category.slug,
122+
mediaType: 'text/asciidoc'
123+
}
124+
}
125+
}
126+
127+
// Index page
128+
129+
function addGraphGistIndexPage(graphGists, pages, contentCatalog, asciidocConfig) {
130+
pages.push(contentCatalog.addFile(createGraphGistIndexPage(graphGists, contentCatalog, asciidocConfig)))
131+
}
132+
133+
function createGraphGistIndexPage(graphGists, contentCatalog, asciidocConfig) {
134+
const featuredPages = []
135+
for (const graphGist of graphGists) {
136+
if (graphGist.featured) {
137+
const page = getById(contentCatalog, {
138+
family: 'page',
139+
relative: `${graphGist.slug}.adoc`
140+
})
141+
if (page) {
142+
featuredPages.push(page)
143+
}
144+
}
145+
}
146+
const categoryPagesMap = generateCategoryPagesMap(graphGists, contentCatalog)
147+
const asciidocAttributes = {
148+
...asciidocConfig.attributes,
149+
'page-layout': 'graphgist-index',
150+
'page-industries': Array.from(categoryPagesMap.values()).filter((value) => value.type === 'industry'),
151+
'page-use-cases': Array.from(categoryPagesMap.values()).filter((value) => value.type === 'use-case'),
152+
'page-featured-pages': featuredPages
153+
}
154+
return {
155+
title: `Neo4j GraphGists`,
156+
asciidoc: {
157+
attributes: asciidocAttributes
158+
},
159+
src: {
160+
component: componentName,
161+
version: componentVersion,
162+
module: componentModule,
163+
family: 'page',
164+
relative: `index.adoc`,
165+
stem: 'index',
166+
mediaType: 'text/asciidoc'
167+
}
168+
}
169+
}
170+
171+
function generateCategoryPagesMap(graphGists, contentCatalog) {
172+
const categories = getCategories(graphGists)
173+
.map((category) => {
174+
const page = getById(contentCatalog, {
175+
family: 'page',
176+
relative: `categories/${category.slug}.adoc`
177+
})
178+
if (page) {
179+
category.pub = page.pub
180+
}
181+
return category
182+
})
183+
const categoryPagesMap = new Map()
184+
for (const category of categories) {
185+
categoryPagesMap.set(category.slug, category)
186+
}
187+
return categoryPagesMap
188+
}
189+
190+
function getCategories(graphGists) {
191+
const industries = new Set()
192+
const useCases = new Set()
193+
for (const graphGist of graphGists) {
194+
if (graphGist.industries && graphGist.industries.length > 0) {
195+
for (const industry of graphGist.industries) {
196+
industries.add(JSON.stringify(industry))
197+
}
198+
}
199+
if (graphGist.use_cases && graphGist.use_cases.length > 0) {
200+
for (const useCase of graphGist.use_cases) {
201+
useCases.add(JSON.stringify(useCase))
202+
}
203+
}
204+
}
205+
const industryPages = Array.from(industries).map((industry) => {
206+
const industryObj = JSON.parse(industry)
207+
industryObj.imageUrl = industryObj.image && industryObj.image.length > 0
208+
? industryObj.image[0].source_url
209+
: undefined
210+
industryObj.type = 'industry'
211+
return industryObj
212+
})
213+
const useCasePages = Array.from(useCases).map((useCase) => {
214+
const useCaseObj = JSON.parse(useCase)
215+
useCaseObj.imageUrl = useCaseObj.image && useCaseObj.image.length > 0
216+
? useCaseObj.image[0].source_url
217+
: undefined
218+
useCaseObj.type = 'use-case'
219+
return useCaseObj
220+
})
221+
return [...industryPages, ...useCasePages]
222+
}
223+
224+
// GraphGist pages
225+
226+
function addGraphGistPages(graphGists, contentCatalog, asciidocConfig) {
227+
for (const graphGist of graphGists) {
228+
contentCatalog.addFile(createGraphGistPage(graphGist))
229+
}
230+
contentCatalog.registerComponentVersion(componentName, componentVersion)
231+
}
232+
233+
function createGraphGistPage(graphGist) {
234+
return {
235+
title: graphGist.title,
236+
path: `${__dirname}/${graphGist.slug}.adoc`,
237+
contents: Buffer.from(asciidocPreprocessing(graphGist.asciidoc)),
238+
src: {
239+
component: componentName,
240+
version: componentVersion,
241+
module: componentModule,
242+
family: 'page',
243+
relative: `${graphGist.slug}.adoc`,
244+
stem: graphGist.slug,
245+
mediaType: 'text/asciidoc'
246+
}
247+
}
248+
}
249+
250+
// Assign additional attributes on pages
251+
252+
function assignPageAttributes(graphGists, contentCatalog, asciidocConfig) {
253+
const categoryPagesMap = generateCategoryPagesMap(graphGists, contentCatalog)
254+
const allIndustries = Array.from(categoryPagesMap.values()).filter((value) => value.type === 'industry')
255+
const allUseCases = Array.from(categoryPagesMap.values()).filter((value) => value.type === 'use-case')
256+
for (const graphGist of graphGists) {
257+
const page = getById(contentCatalog, {
258+
family: 'page',
259+
relative: `${graphGist.slug}.adoc`
260+
})
261+
if (page) {
262+
const imageUrl = graphGist.image && graphGist.image.length > 0
263+
? graphGist.image[0].source_url
264+
: 'https://dist.neo4j.com/wp-content/uploads/20160303103313/noun_22440-grey-02.png' // default image
265+
const notebookAttachment = getById(contentCatalog, {
266+
family: 'attachment',
267+
relative: `${graphGist.slug}.ipynb`
268+
})
269+
// TODO: generate a browser guide?
270+
const browserGuideAttachment = undefined
271+
const asciidocAttributes = {
272+
...page.asciidoc.attributes,
273+
...asciidocConfig.attributes,
274+
'page-title': graphGist.title,
275+
'page-layout': 'graphgist',
276+
'page-illustration': imageUrl,
277+
'page-uuid': graphGist.uuid,
278+
'page-slug': graphGist.slug,
279+
'page-industries': allIndustries,
280+
'page-use-cases': allUseCases,
281+
'page-browserGuideUrl': `neo4j-desktop://graphapps/neo4j-browser?cmd=play&arg=https://guides.neo4j.com/graph-examples/${graphGist.slug}/graph_guide `
282+
}
283+
if (browserGuideAttachment) {
284+
asciidocAttributes['page-browserGuideUrl'] = browserGuideAttachment.pub.url
285+
}
286+
if (notebookAttachment) {
287+
asciidocAttributes['page-notebookUrl'] = notebookAttachment.pub.url
288+
}
289+
if (graphGist.author) {
290+
asciidocAttributes['page-author'] = graphGist.author.name
291+
}
292+
if (graphGist.summary) {
293+
asciidocAttributes['page-summary'] = graphGist.summary
294+
}
295+
if (graphGist.featured) {
296+
asciidocAttributes['page-featured'] = true
297+
}
298+
page.asciidoc.attributes = asciidocAttributes
299+
page.asciidoc.doctitle = graphGist.title
300+
}
301+
}
302+
}
303+
304+
function getById(contentCatalog, { family, relative }) {
305+
return contentCatalog.getById({
306+
component: componentName,
307+
version: componentVersion,
308+
module: componentModule,
309+
family,
310+
relative,
311+
})
312+
}
313+
314+
module.exports = {
315+
addGraphGistPages,
316+
addGraphGistIndexPage,
317+
addGraphGistCategoryPages,
318+
generateJupyterNotebookAttachments,
319+
assignPageAttributes,
320+
}

0 commit comments

Comments
 (0)