Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import mdx from '@astrojs/mdx'
import sitemap from '@astrojs/sitemap'
import expressiveCode from 'astro-expressive-code'
import typesafeRoutes from 'astro-typesafe-routes'
import { defineConfig } from 'astro/config'
import { defineConfig, envField } from 'astro/config'

import { SITE_METADATA } from './src/utils/constants'

Expand All @@ -14,6 +14,18 @@ export default defineConfig({
},
trailingSlash: 'always',

env: {
schema: {
// The deployed environment, set in Jenkins script
SITE_ENV: envField.enum({
context: 'client',
access: 'public',
values: ['test', 'staging', 'prod'],
default: 'prod',
}),
},
},

markdown: {
remarkRehype: {
footnoteLabelProperties: {
Expand Down
3 changes: 3 additions & 0 deletions ops/Jenkinsfile.cbc-build-deploy-akamai
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pipeline {
}

stage("Run Build script") {
environment {
SITE_ENV = "${params.SITE_ENV}"
}
steps {
sh """
npm run build
Expand Down
3 changes: 3 additions & 0 deletions ops/Jenkinsfile.cbc-build-deploy-prod-akamai
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ pipeline {
}

stage("Run Build script") {
environment {
SITE_ENV = "${params.SITE_ENV}"
}
steps {
sh """
npm run build
Expand Down
10 changes: 6 additions & 4 deletions src/components/icon.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import type { VariantProps } from 'cva'
import { cva } from 'cva'

const iconStyles = cva({
base: 'usa-icon',
base: 'usa-icon text-middle',
variants: {
size: {
2: 'usa-icon--size-2',
3: 'usa-icon--size-3',
4: 'usa-icon--size-4',
5: 'usa-icon--size-5',
Expand All @@ -20,10 +21,11 @@ const iconStyles = cva({
})

type Props = VariantProps<typeof iconStyles> & {
icon: SvgComponent
icon: SvgComponent,
class?: string
}

const { icon: Component, color, size, ...props } = Astro.props
const { icon: Component, color, size, class: className, ...props } = Astro.props
---

<Component {...props} class={iconStyles({ size, color })} aria-hidden="true" />
<Component {...props} class={iconStyles({ size, color, className })} aria-hidden="true" />
22 changes: 19 additions & 3 deletions src/components/link.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import type { RouteId } from 'astro-typesafe-routes/path'
import type { HTMLAttributes } from 'astro/types'
import type { VariantProps } from 'cva'
import type { HrefOrTo } from '#utils/types'
import Icon from './icon.astro'
import FileDownload from '@uswds/uswds/img/usa-icons/file_download.svg'


type Props<T extends RouteId> = Omit<HTMLAttributes<'a'>, 'href'> &
VariantProps<typeof button> & {
showIcon?: boolean
} & HrefOrTo<T>

const downloadExts = ['.csv', '.pdf', '.json', '.docx', '.xlsx', '.xls']

const props = Astro.props as Props<RouteId>

let isExternal = false
let isDownload = false
let computedHref: string

if ('to' in props && props.to) {
Expand All @@ -37,13 +43,23 @@ if ('to' in props && props.to) {
throw new Error('Link component requires either "href" or "to" prop')
}

if(isExternal === false && downloadExts.some(ext => computedHref.endsWith(ext))){
isDownload = true
}

const { variant = 'link', size, showIcon = true, class: className, ...restProps } = props
---

<a
href={computedHref.replace(/\/+$/g, '/')}
target={isExternal ? '_blank' : undefined}
target={(isExternal || isDownload) ? '_blank' : undefined}
rel={isExternal && !computedHref?.includes('cms.gov') ? 'noreferrer' : undefined}
class:list={[button({ variant, size }), isExternal && showIcon ? 'usa-link--external' : '', className]}
{...restProps}><slot /></a
class:list={[
button({ variant, size }),
isExternal && showIcon ? 'usa-link--external' : '',
className
]}
data-tealium={isDownload ? 'download' : undefined}
{...restProps}
><slot />{isDownload ? (<Icon icon={FileDownload} size={2} class="margin-left-05" />) : null}</a
>
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare let utag: any;
6 changes: 5 additions & 1 deletion src/layouts/base-layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import openSansWoff2 from '@fontsource-variable/open-sans/files/open-sans-latin-
import '@fontsource-variable/open-sans'
import '#assets/sass/styles.scss'
import { SITE_METADATA } from '#utils/constants'
import { SITE_ENV } from 'astro:env/client'

type Props = {
title: string
Expand All @@ -16,7 +17,7 @@ type Props = {

const { title, description } = Astro.props

const TEALIUM_ENV = import.meta.env.PROD ? 'prod' : 'dev'
const TEALIUM_ENV = SITE_ENV === 'prod' ? 'prod' : 'dev'
const getAbsolutePath = (path: string) => new URL(path, Astro.url.origin).toString()
---

Expand Down Expand Up @@ -106,5 +107,8 @@ const getAbsolutePath = (path: string) => new URL(path, Astro.url.origin).toStri
e.parentNode.insertBefore(f, e)
})()
</script>
<script>
import '#utils/tealium-click-events'
</script>
</body>
</html>
144 changes: 144 additions & 0 deletions src/utils/tealium-click-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { SITE_METADATA } from "./constants"
const baseUrl = SITE_METADATA.url

// Wait for USWDS js to finish
document.addEventListener('DOMContentLoaded', () => {
const n_a = 'not applicable'

const links = document.querySelectorAll('a')
links.forEach((linkEl) => {
const tocContainer = document.getElementById('toc')
if (!linkEl.hasAttribute('data-tealium') && !tocContainer?.contains(linkEl)) {
linkEl.addEventListener('click', () => {
const currentTarget = linkEl

if (currentTarget.href.startsWith(baseUrl)) {
// internal link clicked
utag.link({
event_name: 'internal_link_clicked',
link_type: 'link_other',
link_url: linkEl.href,
parent_component_heading: n_a,
parent_component_type: n_a,
text: linkEl.textContent,
})
}
else {
// external link clicked
utag.link({
event_name: 'external_link_click',
text: currentTarget.textContent,
link_type: 'link_external',
link_url: currentTarget.href,
parent_component_heading: n_a,
parent_component_type: n_a,
})
}
})
}
})

// button clicks
const buttons = document.querySelectorAll('[data-tealium="copy_button"]')
buttons.forEach((buttonEl) => {
buttonEl.addEventListener('click', () => {
const currentTarget = buttonEl

utag.link({
event_name: 'button_engagement',
button_style: n_a,
button_type: 'button',
link_type: 'link_other',
link_url: n_a,
parent_component_heading: n_a,
parent_component_type: n_a,
text: currentTarget.textContent,
})
})
})

// accordion clicks
const accordions = document.querySelectorAll('[data-tealium="accordion"]')
accordions.forEach((accordionEl) => {
accordionEl.addEventListener('click', () => {
const currentTarget = accordionEl
if (currentTarget.getAttribute('aria-expanded') === 'false') {
utag.link({
event_name: 'accordion_opened',
heading: currentTarget.textContent,
parent_component_heading: n_a,
parent_component_type: n_a,
})
}
})
})

// file downloads
const downloads = document.querySelectorAll('[data-tealium="download"]')
downloads.forEach((downloadEl) => {
downloadEl.addEventListener('click', () => {
const currentTarget = downloadEl as HTMLAnchorElement

utag.link({
event_name: 'file_download',
file_name: currentTarget.pathname.split('/').pop(),
file_extension: currentTarget.href.split('.').pop(),
file_postDate: n_a,
link_type: 'link_download',
link_url: currentTarget.href,
text: currentTarget.textContent,
})
})
})

// Navigation clicks
const mainNavEls = document.querySelectorAll('[data-tealium="main_nav"]')
const identifierEls = document.querySelectorAll('[data-tealium="identifier"]')
const leftRailEls = document.querySelectorAll('[data-tealium="left_rail"]')
const footerEls = document.querySelectorAll('[data-tealium="footer"]')
const tocEls = document.querySelectorAll('[data-tealium="toc"] a');

[
...mainNavEls,
...identifierEls,
...leftRailEls,
...tocEls,
...footerEls,
].forEach((navigationEl) => {
navigationEl.addEventListener('click', () => {
const currentTarget = navigationEl as HTMLAnchorElement
const navigation_type = currentTarget.getAttribute('data-tealium') || 'right_rail'

let heading = n_a

if (navigation_type === 'right_rail') {
heading = 'On this page'
}
else if (navigation_type === 'left_rail') {
const closest = navigationEl?.closest('.usa-sidenav > .usa-sidenav__item')?.querySelector('a')

// Only track "parent" element as heading, ignore if clicked element was the "parent"
if (closest && closest !== navigationEl) {
heading = closest.textContent
}
}
else if (navigation_type === 'main_nav') {
const closest = navigationEl.closest('.usa-nav__primary .usa-nav__primary-item')?.querySelector('button')

// Only track "parent" element as heading, ignore if clicked element was the "parent"
if (closest && closest !== navigationEl) {
heading = closest.textContent
}
}

utag.link({
event_name: 'navigation_clicked',
heading,
link_type: 'link_other',
link_url: currentTarget.href,
navigation_type,
text: currentTarget.innerText,
})
})
})
})