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

Config : Airtable.s + Node for dynamic content importing #50

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3d16eb1
Add airtable dependency, along with new package and package-lock file…
pkane Nov 16, 2023
762a64a
Swap require import for Airtable.js CDN import
curt-mitch-census Nov 16, 2023
cd67fac
Add Curtis Airtable API token
curt-mitch-census Nov 16, 2023
4145e9a
Update base ID
curt-mitch-census Nov 16, 2023
26fc195
Updates to API to get first 6 records of site content
curt-mitch-census Nov 16, 2023
cc80724
Create xdContent object from record fields. Render test data to 'more…
pkane Nov 17, 2023
0646e96
Separate the content and markup creation logic. Create separate condi…
pkane Nov 17, 2023
d4d2420
Test parsing of image content, adding image tags to page.
pkane Nov 21, 2023
0441f45
Merge branch 'master' into at-integration-curtis
pkane Nov 27, 2023
e31ad79
Create node script to read from airtable, write cache json file from …
pkane Nov 27, 2023
0b324dc
Merge branch 'master' of github.com:XDgov/xd.gov into at-integration-…
pkane Nov 28, 2023
367b75a
Create structure for generating markup and writing to news, bios mark…
pkane Nov 28, 2023
cce4276
Use deepCompare function to compare to cache, handle accordingly
pkane Nov 28, 2023
de1f12f
Remove test js from javascript include file
pkane Nov 28, 2023
57a5099
Cleanup logs
pkane Nov 28, 2023
dfbe1f0
Restore master version of home and apply page files.
pkane Nov 28, 2023
712af6e
Create workflow folder and test airtable action
pkane Nov 29, 2023
0366c46
Gemfile lockfile update platforms list
pkane Nov 29, 2023
24092c2
Restore liquid dependency version. Rename airtable action
pkane Nov 29, 2023
d8015f9
Add npm install step to actions
pkane Nov 29, 2023
94350ef
Update airtable.yml
ian-gov Nov 29, 2023
adc8c55
Update airtable.js
ian-gov Nov 29, 2023
fd8164d
Update config file to build from new import collections files. Update…
pkane Nov 29, 2023
64a561c
Merge branch 'at-integration-curtis' of github.com:XDgov/xd.gov into …
pkane Nov 29, 2023
9de6e7f
Adjust markup concatenation in string literal to fix formatting issue…
pkane Nov 30, 2023
ddb8510
Updated cache and markdown files
pkane Nov 30, 2023
f0630b5
Create new layout type for bios. Add simple grid styling.
pkane Nov 30, 2023
ddb6a8d
Restore private key lookup
pkane Nov 30, 2023
ad47270
Update workflow to create new pr after building
pkane Dec 8, 2023
6fc0a3d
Replace pr step with CPR marketplace action
pkane Dec 8, 2023
f234501
Update gitignore with vendor and .bundle folders
pkane Dec 8, 2023
e0714a0
Create new news landing layout type, update config
pkane Dec 8, 2023
12355a8
Update airtable script to include image alt attributes. Commit cache …
pkane Dec 8, 2023
23f0e99
Comment out hiring include within new layouts
pkane Dec 11, 2023
05360e6
Cleanup anonymous functions for consistency.
pkane Dec 11, 2023
4d37ac0
leverage object destructuring for cleaner markup construction functions
pkane Dec 11, 2023
4fb425c
Update airtable workflow with named jekyll build step
pkane Dec 11, 2023
3d2ccbc
Update bios and cache file
pkane Dec 11, 2023
4b744d9
Update ruby version
pkane Dec 13, 2023
ffc4563
Refactor object prototype call to use json stringify and array isarray
pkane Dec 13, 2023
1a5adf4
Airtable console comment
pkane Dec 13, 2023
dcb672d
Fix typeof erroneous change
pkane Dec 13, 2023
6213aa0
Liquid dependency version update
pkane Dec 14, 2023
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
29 changes: 29 additions & 0 deletions .github/workflows/airtable.yml
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]

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ _site/
.sass-cache/
.jekyll-metadata
.jekyll-cache/
node_modules
.bundle/
vendor/
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1.3
3.2.2
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ source "https://rubygems.org"

# Note: when updating Ruby version update update .ruby-version file as well
# to ensure Cloud.gov environment version of Ruby matches
ruby '3.1.3'
ruby '3.2.2'

# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
Expand Down
4 changes: 3 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ GEM
PLATFORMS
arm64-darwin-21
arm64-darwin-22
x64-mingw-ucrt
x64-mingw32
x86_64-darwin-18
x86_64-darwin-21
x86_64-linux

DEPENDENCIES
jekyll (~> 4.2.1)
Expand All @@ -94,7 +96,7 @@ DEPENDENCIES
webrick

RUBY VERSION
ruby 3.1.3p185
ruby 3.2.2p53
Copy link
Member

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).

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, and actually I refrained from committing the .Gemfile with the version change of 3.1.3 -> 3.2.2.
Should I include that as well?

Copy link
Member

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.


BUNDLED WITH
2.4.16
2 changes: 2 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ collections:
blog:
output: true
permalink: /blog/:permalink/
import:
output: true
collections_dir: collections

defaults:
Expand Down
39 changes: 39 additions & 0 deletions _layouts/bios.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="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 %} -->
39 changes: 39 additions & 0 deletions _layouts/news-landing.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 %} -->
290 changes: 290 additions & 0 deletions airtable-cache.json

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions airtable.js
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');
Copy link
Member

Choose a reason for hiding this comment

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

Should we make the base ID an env variable as well?


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)){
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I should be more explicit for this line since the JSON.stringify method shouldn't ever return '[object Object]'. Here we should probably use typeof like so:

typeof arg1 === 'object'  || Array.isArray(arg1))

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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');
});
})
3 changes: 2 additions & 1 deletion assets/css/_pages/_all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
@import "apply";
@import "home";
@import "privacy";
@import "projects";
@import "projects";
@import "bios";
34 changes: 34 additions & 0 deletions assets/css/_pages/_bios.scss
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;
}
}
}
}
Loading
Loading