-
Notifications
You must be signed in to change notification settings - Fork 3
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
Config : Airtable.s + Node for dynamic content importing #50
Changes from 41 commits
3d16eb1
762a64a
cd67fac
4145e9a
26fc195
cc80724
0646e96
d4d2420
0441f45
e31ad79
0b324dc
367b75a
cce4276
de1f12f
57a5099
dfbe1f0
712af6e
0366c46
24092c2
d8015f9
94350ef
adc8c55
fd8164d
64a561c
9de6e7f
ddb8510
f0630b5
ddb6a8d
ad47270
6fc0a3d
f234501
e0714a0
12355a8
23f0e99
05360e6
4d37ac0
4fb425c
3d2ccbc
4b744d9
ffc4563
1a5adf4
dcb672d
6213aa0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
name: Airtable Import | ||
on: [push] | ||
jobs: | ||
airtable: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Use Node.js 18.x | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18.x | ||
- uses: ruby/setup-ruby@v1 | ||
with: | ||
bundler-cache: true | ||
- name: npm install | ||
run: | | ||
npm install | ||
- name: run Airtable Import | ||
run: | | ||
node airtable.js | ||
env: | ||
AIRTABLE_API_KEY: ${{secrets.AIRTABLE_API_KEY}} | ||
- name: bundle install and build Jekyll | ||
run: | | ||
bundle install | ||
bundle exec jekyll build | ||
- name: Create Pull Request | ||
uses: peter-evans/[email protected] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,6 @@ _site/ | |
.sass-cache/ | ||
.jekyll-metadata | ||
.jekyll-cache/ | ||
node_modules | ||
.bundle/ | ||
vendor/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
3.1.3 | ||
3.2.2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
--- | ||
layout: default | ||
--- | ||
<section class="story"> | ||
<article class="grid-container"> | ||
<div class="grid-row grid-gap-lg news-row"> | ||
<div class="news-content-container"> | ||
<header> | ||
<div class="breadcrumb">Team Bios</div> | ||
<h1>{{ page.title }}</h1> | ||
<p class="news-date">{{ page.publish_date | date: '%B %d, %Y' }}</p> | ||
</header> | ||
<div class="bios-content"> | ||
{{ content }} | ||
</div> | ||
</div> | ||
</div> | ||
</article> | ||
</section> | ||
<section class="more-news"> | ||
{% assign site_news = site.news %} | ||
<div class="grid-container"> | ||
<div class="breadcrumb">More News</div> | ||
<div class="grid-row grid-gap-lg"> | ||
{% for news in site_news limit:4 %} | ||
{% if news.url != page.url %} | ||
{% if forloop.index < 3 or found %} | ||
<div class="tablet:grid-col-6"> | ||
{% include components/news-item.html news=news %} | ||
</div> | ||
{% endif %} | ||
{% else %} | ||
{% assign found = true %} | ||
{% endif %} | ||
{% endfor %} | ||
</div> | ||
</div> | ||
</section> | ||
<!-- {% include components/hiring.html %} --> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
--- | ||
layout: default | ||
--- | ||
<section class="story"> | ||
<article class="grid-container"> | ||
<div class="grid-row grid-gap-lg news-row"> | ||
<div class="tablet:grid-col-12 news-content-container"> | ||
<header> | ||
<div class="breadcrumb">News</div> | ||
<h1>{{ page.title }}</h1> | ||
<p class="news-date">{{ page.publish_date | date: '%B %d, %Y' }}</p> | ||
</header> | ||
<div class="news-content"> | ||
{{ content }} | ||
</div> | ||
</div> | ||
</div> | ||
</article> | ||
</section> | ||
<section class="more-news"> | ||
{% assign site_news = site.news %} | ||
<div class="grid-container"> | ||
<div class="breadcrumb">More News</div> | ||
<div class="grid-row grid-gap-lg"> | ||
{% for news in site_news limit:4 %} | ||
{% if news.url != page.url %} | ||
{% if forloop.index < 3 or found %} | ||
<div class="tablet:grid-col-6"> | ||
{% include components/news-item.html news=news %} | ||
</div> | ||
{% endif %} | ||
{% else %} | ||
{% assign found = true %} | ||
{% endif %} | ||
{% endfor %} | ||
</div> | ||
</div> | ||
</section> | ||
<!-- {% include components/hiring.html %} --> |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
const fs = require('fs'); | ||
const Airtable = require('airtable'); | ||
|
||
const base = new Airtable({apiKey: process.env.AIRTABLE_API_KEY}).base('appuZMt69pZnTis2t'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make the base ID an |
||
|
||
const xdContent = {}; | ||
const cacheFilePath = './airtable-cache.json'; | ||
const newsFilePath = './collections/_import/news.md'; | ||
const biosFilePath = './collections/_import/bios.md'; | ||
|
||
// Utility function we'll use to compare our data | ||
const deepCompare = (arg1, arg2) => { | ||
if (JSON.stringify(arg1) === JSON.stringify(arg2)){ | ||
if (JSON.stringify(arg1) === '[object Object]' || Array.isArray(arg1)){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I should be more explicit for this line since the typeof arg1 === 'object' || Array.isArray(arg1)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops, sorry about that! I haphazardly grouped that in with the other changes. |
||
if (Object.keys(arg1).length !== Object.keys(arg2).length ){ | ||
return false; | ||
} | ||
return (Object.keys(arg1).every((key) => { | ||
return deepCompare(arg1[key],arg2[key]); | ||
})); | ||
} | ||
return (arg1===arg2); | ||
} | ||
return false; | ||
} | ||
|
||
// Fetch our airtable content and generate some markup with it | ||
// Optionally (if newer), write to our cache file with new data | ||
curt-mitch-census marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const fetchAirtablePromise = (path) => new Promise((resolve, reject) => { | ||
|
||
base('xd.gov Content').select({ | ||
// Selecting the first 3 records in Grid view: | ||
maxRecords: 20, | ||
view: "Grid view" | ||
}).eachPage((records, fetchNextPage) => { | ||
// This function will get called for each page of records. | ||
// Grab only content with a content type field | ||
const filteredRecords = records.filter(record => record.fields['Content Type'] !== undefined); | ||
// Filter content types to set as xdContent keys | ||
const xdFieldNames = new Set(filteredRecords.filter(record => record.fields['Content Type'] !== undefined).map(record => record.fields['Content Type'][0])); | ||
xdFieldNames.forEach(name => xdContent[name] = []); | ||
|
||
filteredRecords.forEach((record) => { | ||
let fieldType = record.fields['Content Type']; | ||
xdContent[fieldType].push(record.fields); | ||
}); | ||
|
||
// If there are more records, `page` will get called again. | ||
// If there are no more records, `done` will get called. | ||
fetchNextPage(); | ||
|
||
resolve(xdContent) | ||
|
||
}, function done(err) { | ||
if (err) { console.error(err); reject(error); return; } | ||
}); | ||
|
||
}); | ||
|
||
const generateXdMarkup = (content) => { | ||
let newsMarkDown = '---\n' + 'layout: news-landing\n' + 'title: News\n' + '---'; | ||
|
||
// Create News page elements | ||
content['News'].forEach(({ Name: name, Blurb: blurb }) => { | ||
newsMarkDown += `\n<div>\n<h3>${name}</h3>\n<p>${blurb}</p>\n</div>`; | ||
}) | ||
|
||
let biosMarkdown = '---\n' + 'layout: bios\n' + 'title: Bios\n' + '---'; | ||
|
||
// Create Bios page elements | ||
content['Bio for team page'].forEach(({ Name: name, Blurb: blurb, Images: images }) => { | ||
if ([name, blurb, images].every(item => item !== undefined)) { | ||
biosMarkdown += `\n<div>\n<img id="${images[0].id}" alt="Image of ${name}" src="${images[0].url}" />\n<h3>${name}</h3>\n<p>${blurb}</p>\n</div>` | ||
} | ||
}) | ||
|
||
// Keep log for Action debugging | ||
console.log(newsMarkDown, biosMarkdown); | ||
curt-mitch-census marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return [newsMarkDown, biosMarkdown]; | ||
} | ||
|
||
fetchAirtablePromise(cacheFilePath, newsFilePath, biosFilePath) | ||
.then((data) => { | ||
|
||
const cacheData = fs.readFileSync(cacheFilePath); | ||
|
||
// Compare our cache with the newly fetched data. | ||
// If the same, we don't need to continue. | ||
if (deepCompare(JSON.parse(cacheData), data)) { | ||
console.log('Data is a match to cache, aborting.'); | ||
return; | ||
} | ||
|
||
const markup = generateXdMarkup(data); | ||
|
||
// Write to json airtable-cache file | ||
fs.writeFile(cacheFilePath, JSON.stringify(data, null, 2), (error) => { | ||
if (error) { | ||
console.log('An error has occurred ', error); | ||
return; | ||
} | ||
console.log('Data written successfully to disk'); | ||
}); | ||
|
||
// Write to json airtable-cache file | ||
fs.writeFile(newsFilePath, markup[0], (error) => { | ||
if (error) { | ||
console.log('An error has occurred ', error); | ||
return; | ||
} | ||
console.log('News markup written successfully to disk'); | ||
}); | ||
|
||
// Write to json airtable-cache file | ||
fs.writeFile(biosFilePath, markup[1], (error) => { | ||
if (error) { | ||
console.log('An error has occurred ', error); | ||
return; | ||
} | ||
console.log('Bios markup written successfully to disk'); | ||
}); | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,5 @@ | |
@import "apply"; | ||
@import "home"; | ||
@import "privacy"; | ||
@import "projects"; | ||
@import "projects"; | ||
@import "bios"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.page-bios { | ||
.bios-content { | ||
display: grid; | ||
grid-template-columns: 100%; | ||
width: 100%; | ||
grid-gap: 12px; | ||
|
||
@media only screen and(min-width: $tablet-size) { | ||
grid-template-columns: 1fr 1fr; | ||
|
||
} | ||
|
||
@media only screen and(min-width: $desktop-size) { | ||
grid-template-columns: 1fr 1fr 1fr; | ||
|
||
} | ||
|
||
> div { | ||
padding: 16px; | ||
background: #f7f7f7; | ||
display: flex; | ||
flex-flow: column; | ||
|
||
img { | ||
max-height: 200px; | ||
align-self: center; | ||
} | ||
|
||
h3 { | ||
margin-bottom: 0; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the site still build with this version of Ruby? I ask because there was a Jekyll bug that caused us to fall back to 3.1.3. If it looks like everything is working as expected, we can go ahead and migrate to 3.2.2, in which case we should update the
.ruby-version
file as well since that's what cloud.gov uses when it builds the site (and we'll know if the version is still breaking with the Pages build Action step).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and actually I refrained from committing the
.Gemfile
with the version change of3.1.3
->3.2.2
.Should I include that as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's go ahead and update both the
Gemfile
and.ruby-version
.