Skip to content

Commit

Permalink
VACMS-16290 Event Listing (#322)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Ryan Koch <[email protected]>
Co-authored-by: Tanner Heffner <[email protected]>
Co-authored-by: Tim Cosgrove <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tanner Heffner <[email protected]>
  • Loading branch information
6 people authored Jan 22, 2024
1 parent 50a564e commit 65f6e78
Show file tree
Hide file tree
Showing 33 changed files with 1,736 additions and 92 deletions.
22 changes: 22 additions & 0 deletions playwright/tests/eventListing.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { test, expect } = require('../utils/next-test')

test.describe('eventListing', () => {
test('Event Listing page renders with events that can be navigated to', async ({
page,
}) => {
await page.goto('/butler-health-care/events')
await page.locator('.events a').first().click()
await expect(page).toHaveURL(/\/butler-health-care\/events\//)
})

test('Should render without a11y errors', async ({
page,
makeAxeBuilder,
}) => {
await page.goto('/butler-health-care/events')

const accessibilityScanResults = await makeAxeBuilder().analyze()

expect(accessibilityScanResults.violations).toEqual([])
})
})
4 changes: 2 additions & 2 deletions plopfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ module.exports = function (plop) {
'You will need to do a few steps manually:',
'- Import & add your query to src/data/queries/index.ts',
'- Add your resource type to src/lib/constants/resourceTypes.ts',
'- Update the mock.json with correct data',
'- Update the mock.json with correct data. See src/pages/_playgroud/api-explorer.tsx',
'- Run `yarn test:u` to update test snapshots for your new query!',
],
})
Expand Down Expand Up @@ -107,7 +107,7 @@ module.exports = function (plop) {
'You will need to do a few steps manually:',
'- Import & add your query to src/data/queries/index.ts',
'- Add your resource type to src/lib/constants/resourceTypes.ts',
'- Update the mock.json with correct data',
'- Update the mock.json with correct data. See src/pages/_playgroud/api-explorer.tsx',
'- Run `yarn test:u` to update test snapshots for your new query!',
// Create react component + test files for new Page type.
{
Expand Down
105 changes: 105 additions & 0 deletions src/data/queries/eventListing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { QueryData, QueryFormatter, QueryParams } from 'next-drupal-query'
import { queries } from '.'
import { NodeEvent, NodeEventListing } from '@/types/drupal/node'
import { Menu } from '@/types/drupal/menu'
import { EventListing } from '@/types/formatted/eventListing'
import { RESOURCE_TYPES } from '@/lib/constants/resourceTypes'
import { PAGE_SIZES } from '@/lib/constants/pageSizes'
import { ExpandedStaticPropsContext } from '@/lib/drupal/staticProps'
import {
entityBaseFields,
fetchAndConcatAllResourceCollectionPages,
fetchSingleEntityOrPreview,
getMenu,
} from '@/lib/drupal/query'
import { buildSideNavDataFromMenu } from '@/lib/drupal/facilitySideNav'

const PAGE_SIZE = PAGE_SIZES[RESOURCE_TYPES.EVENT_LISTING]

// Define the query params for fetching node--event_listing.
export const params: QueryParams<null> = () => {
return queries.getParams().addInclude(['field_office'])
}

// Define the option types for the data loader.
export type EventListingDataOpts = {
id: string
context?: ExpandedStaticPropsContext
}

type EventListingData = {
entity: NodeEventListing
events: NodeEvent[]
menu?: Menu
totalItems: number
totalPages: number
}

const listingParams: QueryParams<string> = (listingEntityId: string) => {
return queries
.getParams('node--event--teaser')
.addFilter('field_listing.id', listingEntityId)
.addSort('-created')
}

// Implement the data loader.
export const data: QueryData<EventListingDataOpts, EventListingData> = async (
opts
) => {
const entity = (await fetchSingleEntityOrPreview(
opts,
RESOURCE_TYPES.EVENT_LISTING,
params
)) as NodeEventListing

// Fetch list of events related to this listing
const { data: events } =
await fetchAndConcatAllResourceCollectionPages<NodeEvent>(
RESOURCE_TYPES.EVENT,
listingParams(entity.id),
PAGE_SIZE
)

// Fetch the menu name dynamically off of the field_office reference if available.
// The `/outreach-and-events/events` event listing page has no menu attached to it.
let menu = null
if (entity.field_office.field_system_menu) {
menu = await getMenu(
entity.field_office.field_system_menu.resourceIdObjMeta
.drupal_internal__target_id
)
}

return {
entity,
events,
menu,
totalItems: events.length,
totalPages: 1, // We don't want to paginate event listing pages. The widget handles pagination.
}
}

export const formatter: QueryFormatter<EventListingData, EventListing> = ({
entity,
events,
menu,
totalItems,
totalPages,
}) => {
const formattedEvents = events.map((event) => {
return queries.formatData('node--event--teaser', event)
})

let formattedMenu = null
if (menu !== null)
formattedMenu = buildSideNavDataFromMenu(entity.path.alias, menu)

return {
...entityBaseFields(entity),
introText: entity.field_intro_text,
events: formattedEvents,
menu: formattedMenu,
totalItems,
totalPages,
}
}
97 changes: 97 additions & 0 deletions src/data/queries/eventTeaser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { QueryFormatter, QueryParams } from 'next-drupal-query'
import { queries } from '.'
import { NodeEvent } from '@/types/drupal/node'
import { EventWidgetTeaser } from '@/types/formatted/event'
import { formatDateObject } from '@/lib/utils/date'

// Define the query params for fetching node--event--teaser.
export const params: QueryParams<null> = () => {
return queries
.getParams()
.addInclude([
'field_listing',
'field_administration',
'field_facility_location',
])
}

// This formats Event nodes in the manner that the events widget from vets-website expects.
export const formatter: QueryFormatter<NodeEvent, EventWidgetTeaser> = (
entity: NodeEvent
) => {
const time = formatDateObject(entity.field_datetime_range_timezone)

return {
changed: entity.changed,
entityBundle: entity.type,
entityId: entity.id,
entityPublished: entity.status,
entityUrl: {
path: entity.path.alias,
},
fieldAdditionalInformationAbo: entity.field_additional_information_abo,
fieldAdditionalListings: null,
fieldAddress: {
addressLine1: entity.field_address?.address_line1 || null,
addressLine2: entity.field_address?.address_line2 || null,
administrativeArea: entity.field_address?.administrative_area || null,
countryCode: entity.field_address?.country_code || null,
locality: entity.field_address?.locality || null,
postalCode: entity.field_address?.postal_code || null,
},
fieldAdministration: {
entity: {
entityId: entity.field_administration.id,
},
},
fieldBody: entity.field_body,
fieldCtaEmail: entity.field_cta_email,
fieldDatetimeRangeTimezone: time,
fieldDescription: entity.field_description,
fieldEventCost: entity.field_event_cost,
fieldEventCta: entity.field_event_cta,
fieldEventRegistrationrequired: entity.field_event_registrationrequired,
fieldFacilityLocation: entity.field_facility_location
? {
entity: {
entityUrl: {
path: entity.field_facility_location.path?.alias || null,
},
fieldAddress: {
addressLine1:
entity.field_facility_location.field_address?.address_line1 ||
null,
addressLine2:
entity.field_facility_location.field_address?.address_line2 ||
null,
administrativeArea:
entity.field_facility_location.field_address
?.administrative_area || null,
countryCode:
entity.field_facility_location.field_address?.country_code ||
null,
locality:
entity.field_facility_location.field_address?.locality || null,
postalCode:
entity.field_facility_location.field_address?.postal_code ||
null,
},
title: entity.field_facility_location?.title || null,
},
}
: null,
fieldFeatured: entity.field_featured,
fieldHowToSignUp: entity.field_how_to_sign_up,
fieldLink: entity.field_link,
fieldListing: {
entity: {
entityId: entity.field_listing.id,
},
},
fieldLocationHumanreadable: entity.field_location_humanreadable,
fieldLocationType: entity.field_location_type,
fieldOrder: entity.field_order,
fieldUrlOfAnOnlineEvent: entity.field_url_of_an_online_event,
title: entity.title,
}
}
4 changes: 4 additions & 0 deletions src/data/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import * as StaticPathResources from './staticPathResources'
import * as HeaderFooter from './headerFooter'
import * as PromoBlock from './promoBlock'
import * as Event from './event'
import * as EventTeaser from './eventTeaser'
import * as EventListing from './eventListing'
import * as VamcEhr from './vamcEhr'
import { ResourceType } from '@/lib/constants/resourceTypes'

Expand All @@ -28,6 +30,8 @@ export const QUERIES_MAP = {
'node--story_listing': StoryListing,
'node--q_a': QuestionAnswer,
'node--event': Event,
'node--event--teaser': EventTeaser,
'node--event_listing': EventListing,
'node--person_profile': PersonProfile,
'node--landing_page': BenefitsHub, // "Benefits Hub Landing Page"
'paragraph--audience_topics': AudienceTopics,
Expand Down
2 changes: 1 addition & 1 deletion src/data/queries/storyListing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {

const PAGE_SIZE = PAGE_SIZES[RESOURCE_TYPES.STORY_LISTING]

// Define the query params for fetching node--news_story.
// Define the query params for fetching node--story_listing.
export const params: QueryParams<null> = () => {
return queries.getParams().addInclude(['field_office'])
}
Expand Down
Loading

0 comments on commit 65f6e78

Please sign in to comment.