diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..b4ff0e40
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+export IDP_BASE_URL_PREVIEW=http://localhost:3000
diff --git a/.gitignore b/.gitignore
index f3daba9e..4e19eb59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,5 @@ node_modules/
.DS_Store
.vscode
.idea
+.env
-# pulled dynamically in the build/deploy process
-_data/team.yml
diff --git a/Gemfile b/Gemfile
index e833e03a..5cb7f409 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,7 @@ gem "kramdown", ">= 2.3.0"
group :jekyll_plugins do
gem "jekyll-redirect-from"
gem "jekyll-last-modified-at"
+ gem "jekyll-environment-variables"
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 2e3229df..89793a47 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -46,6 +46,8 @@ GEM
rouge (~> 3.0)
safe_yaml (~> 1.0)
terminal-table (~> 1.8)
+ jekyll-environment-variables (1.0.1)
+ jekyll (>= 3.0, < 5.x)
jekyll-last-modified-at (1.3.0)
jekyll (>= 3.7, < 5.0)
posix-spawn (~> 0.3.9)
@@ -117,6 +119,7 @@ DEPENDENCIES
activesupport
html-proofer
jekyll (~> 4)
+ jekyll-environment-variables
jekyll-last-modified-at
jekyll-redirect-from
kramdown (>= 2.3.0)
diff --git a/Makefile b/Makefile
index 8199fbf6..1c30836d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
-run: setup
- bundle exec jekyll serve --watch
+run: setup .env
+ /bin/bash -c "source .env && bundle exec jekyll serve --watch"
-build: setup
- bundle exec jekyll build
+build: setup .env
+ /bin/bash -c "source .env && bundle exec jekyll build"
setup:
bundle
@@ -15,4 +15,7 @@ test: build
clean:
rm -rf _site
+.env:
+ cp -n .env.example .env
+
.PHONY: setup clean
diff --git a/_articles/analytics-events.md b/_articles/analytics-events.md
new file mode 100644
index 00000000..eaf078c7
--- /dev/null
+++ b/_articles/analytics-events.md
@@ -0,0 +1,35 @@
+---
+title: "Analytics Events"
+description: "Event descriptions"
+layout: article
+category: Reporting
+---
+
+{% if site.env.BRANCH == 'main' %}
+ {% assign idp_base_url = site.env.IDP_BASE_URL %}
+{% else %}
+ {% assign idp_base_url = site.env.IDP_BASE_URL_PREVIEW %}
+{% endif %}
+{% assign idp_base_url = idp_base_url | default: 'https://secure.login.gov' %}
+
+These are the events that are documented in the IDP. Each event has can have custom
+properties that go under `event_properties` in the final payload:
+
+### Events
+
+```json
+{
+ "name": "Event Name",
+ "user_id": "some-user-id",
+ "properties": {
+ "event_properties": {}
+ }
+}
+```
+
+
+
+
+
\ No newline at end of file
diff --git a/_articles/queries.md b/_articles/queries.md
index 26f32e71..20a7e394 100644
--- a/_articles/queries.md
+++ b/_articles/queries.md
@@ -2,7 +2,7 @@
title: "Reporting Queries"
description: "Queries to run in the Rails console for common reporting questions"
layout: article
-category: "AppDev"
+category: "Reporting"
---
## Query timeout
diff --git a/_config.yml b/_config.yml
index 79aba856..13b5849b 100644
--- a/_config.yml
+++ b/_config.yml
@@ -15,10 +15,13 @@ copy_to_destination:
- node_modules/identity-style-guide/dist/assets
- node_modules/anchor-js/anchor.min.js
- node_modules/@18f/private-eye/private-eye.js
+ - node_modules/preact/dist/preact.module.js
+ - node_modules/htm/dist/htm.module.js
plugins:
- jekyll-redirect-from
- jekyll-last-modified-at
+ - jekyll-environment-variables
exclude:
- CONTRIBUTING.md
diff --git a/_layouts/article.html b/_layouts/article.html
index 4c4d36cf..bc29b616 100644
--- a/_layouts/article.html
+++ b/_layouts/article.html
@@ -10,7 +10,7 @@
html=content
sanitize=true
class="inline_toc usa-accordion usa-sidenav"
- id="my_toc"
+ id="sidenav"
item_class="usa-sidenav__item"
submenu_class="usa-sidenav__sublist"
h_min=2
diff --git a/assets/js/analytics-events.js b/assets/js/analytics-events.js
new file mode 100644
index 00000000..bf5cc177
--- /dev/null
+++ b/assets/js/analytics-events.js
@@ -0,0 +1,204 @@
+import { h, Component, render } from '../../preact.module.js';
+import htm from '../../htm.module.js';
+
+// Copied from https://github.com/bryanbraun/anchorjs
+const urlify = new function() {
+ // hax to make the below copy-paste more easily
+ this.options = { truncate: 64 };
+
+ /**
+ * Urlify - Refine text so it makes a good ID.
+ *
+ * To do this, we remove apostrophes, replace non-safe characters with hyphens,
+ * remove extra hyphens, truncate, trim hyphens, and make lowercase.
+ *
+ * @param {String} text - Any text. Usually pulled from the webpage element we are linking to.
+ * @return {String} - hyphen-delimited text for use in IDs and URLs.
+ */
+ this.urlify = function(text) {
+ // Decode HTML characters such as ' ' first.
+ var textareaElement = document.createElement('textarea');
+ textareaElement.innerHTML = text;
+ text = textareaElement.value;
+
+ // Regex for finding the non-safe URL characters (many need escaping):
+ // & +$,:;=?@"#{}|^~[`%!'<>]./()*\ (newlines, tabs, backspace, vertical tabs, and non-breaking space)
+ var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g;
+
+ // The reason we include this _applyRemainingDefaultOptions is so urlify can be called independently,
+ // even after setting options. This can be useful for tests or other applications.
+ if (!this.options.truncate) {
+ _applyRemainingDefaultOptions(this.options);
+ }
+
+ // Note: we trim hyphens after truncating because truncating can cause dangling hyphens.
+ // Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
+ return text.trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
+ .replace(/'/gi, '') // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
+ .replace(nonsafeChars, '-') // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-"
+ .replace(/-{2,}/g, '-') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-"
+ .substring(0, this.options.truncate) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-"
+ .replace(/^-+|-+$/gm, '') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated"
+ .toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated"
+ };
+
+ // hax to make the above copy-paste more easily
+ this.urlify = this.urlify.bind(this);
+}().urlify;
+
+window.urlify = urlify;
+
+const html = htm.bind(h);
+
+function Anchor({ slug, icon = String.fromCharCode(59851) }) {
+ const setRef = (dom) => {
+ if (dom && document.location.hash.slice(1) === slug) {
+ setTimeout(() => dom.scrollIntoView(), 0);
+ }
+ }
+
+ return html`
+ `;
+}
+
+function Example({ event_name, attributes }) {
+ function typeExample(type) {
+ switch(type) {
+ case 'Boolean':
+ return ['true', 'false'];
+ case 'Integer':
+ return 0;
+ case 'Hash':
+ return '{}';
+ default:
+ return type;
+ }
+ }
+
+ const attributeExamples = attributes.flatMap(({ name, types, description }) => {
+ const value = types.flatMap((type) => typeExample(type)).join(' | ');
+
+ let example = [`${name}: ${value},`];
+
+ if (description) {
+ example.unshift(`// ${description}`);
+ }
+
+ return example;
+ });
+
+ if (attributeExamples.length) {
+ attributeExamples.unshift('');
+ attributeExamples.push('');
+ }
+
+ const eventProperties = attributeExamples.
+ join("\n ").
+ replace(/( $)/, ''); // fix last line indentation
+
+ const example = `{
+ name: ${JSON.stringify(event_name)},
+ properties: {
+ event_properties: {${eventProperties}}
+ }
+}`;
+
+ return html`${example}`;
+}
+
+function Attribute({ name, types, description }) {
+ return html`
+
+ ${name}
+ ${ types?.length ? html`${' '}(${types.join(', ')})` : undefined }
+ ${description}
+
+ `;
+}
+
+function Event({ event_name, description, attributes = [] }) {
+ return html`
+
+
+ ${event_name}
+ <${Anchor} slug=${urlify(event_name)} />
+
+
${description}
+
+ ${
+ attributes?.length ? html`
+
+ Attributes
+ <${Anchor} slug=${urlify(event_name + ' Attributes')} />
+
+
+ Show attribute details
+
+ ${attributes.map((attribute) => html`<${Attribute} ...${attribute} />` )}
+
+
+ ` : undefined
+ }
+
+
+ Example
+ <${Anchor} slug=${urlify(event_name + ' Example')} />
+
+ <${Example} event_name=${event_name} attributes=${attributes} />
+
+ `;
+}
+
+function Events({ events }) {
+ return html`${events.map((event) =>
+ html`<${Event} ...${event} />`
+ )}`;
+}
+
+function SidebarNavItem({ name }) {
+ return html`
+
+ ${name}
+
+ `;
+}
+
+function ErrorPage({ error, url }) {
+ return html`
+
+
+
Error loading event definitions
+
+
There was an error loading event definitions from ${url}:
+
${error.message}
+
+
+
+ `
+}
+
+function Sidenav({ events }) {
+ return html`${events.map(({ event_name: name }) =>
+ html`<${SidebarNavItem} name=${name} />`
+ )}`;
+}
+
+const container = document.querySelector('#events-container');
+const { idpBaseUrl } = container.dataset;
+const eventsUrl = `${idpBaseUrl}/api/analytics-events`;
+
+const sidenav = document.querySelector('#sidenav');
+
+window.fetch(eventsUrl)
+ .then((response) => response.json())
+ .then(({ events }) => {
+ render(html`<${Events} events=${events} />`, container);
+ render(html`<${Sidenav} events=${events} />`, sidenav);
+ })
+ .catch((error) => render(html`<${ErrorPage} url=${eventsUrl} error=${error} />`, container));
diff --git a/package-lock.json b/package-lock.json
index b2b71b4e..1c5e9b2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,9 @@
"dependencies": {
"@18f/private-eye": "^2.0.0",
"anchor-js": "^4.2.2",
- "identity-style-guide": "^6.3.0"
+ "htm": "^3.1.0",
+ "identity-style-guide": "^6.3.0",
+ "preact": "^10.6.6"
}
},
"node_modules/@18f/private-eye": {
@@ -38,6 +40,11 @@
"node": ">=4.0.0"
}
},
+ "node_modules/htm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.0.tgz",
+ "integrity": "sha512-L0s3Sid5r6YwrEvkig14SK3Emmc+kIjlfLhEGn2Vy3bk21JyDEes4MoDsbJk6luaPp8bugErnxPz86ZuAw6e5Q=="
+ },
"node_modules/identity-style-guide": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/identity-style-guide/-/identity-style-guide-6.3.0.tgz",
@@ -69,6 +76,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/preact": {
+ "version": "10.6.6",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz",
+ "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
"node_modules/receptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/receptor/-/receptor-1.0.0.tgz",
@@ -127,6 +143,11 @@
"resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz",
"integrity": "sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw="
},
+ "htm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.0.tgz",
+ "integrity": "sha512-L0s3Sid5r6YwrEvkig14SK3Emmc+kIjlfLhEGn2Vy3bk21JyDEes4MoDsbJk6luaPp8bugErnxPz86ZuAw6e5Q=="
+ },
"identity-style-guide": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/identity-style-guide/-/identity-style-guide-6.3.0.tgz",
@@ -151,6 +172,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
+ "preact": {
+ "version": "10.6.6",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz",
+ "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw=="
+ },
"receptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/receptor/-/receptor-1.0.0.tgz",
diff --git a/package.json b/package.json
index 6535c43b..e8af700a 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,8 @@
"dependencies": {
"@18f/private-eye": "^2.0.0",
"anchor-js": "^4.2.2",
- "identity-style-guide": "^6.3.0"
+ "htm": "^3.1.0",
+ "identity-style-guide": "^6.3.0",
+ "preact": "^10.6.6"
}
}