department-of-veterans-affairs / va.gov-cms

Editor-centered management for Veteran-centered content.
https://prod.cms.va.gov
GNU General Public License v2.0
98 stars 68 forks source link

FE: Drupalize Footer #11482

Closed ryguyk closed 1 year ago

ryguyk commented 1 year ago

Description

This ticket is the result of discovery ticket https://github.com/department-of-veterans-affairs/va.gov-cms/issues/10512.

We are changing the hard-coded json structure of the footer menus to use Drupal as the source of truth for data / URLs. The footer is comprised of several parts:

  1. Top section of 4 columns
    • Cols 1-3 managed via va-gov-footer Drupal menu in the CMS. In scope for this ticket.
    • Col 4 includes Veterans Crisis Line, Get Answers, Call Us, and Facility Locator. Not in scope here. This is a special case that needs more review.
  2. Language selectors - Not in scope of this ticket, more info below
  3. Footer Bottom rail - in scope, managed via footer-bottom-rail Drupal menu

Data: The back-end ticket defined the menus to be queried by content build. This ticket amounts to utilizing the provided menu(s) rather than hard-coding footer data in the front-end code. @chri5tia is point of contact for GraphQL questions.

1/26: #12176 will update the graphQL listed below, and is a blocker for finishing this ticket

GraphQL and example data for VA Gov Footer Menu: ```graphql fragment MenuItem on MenuLink { expanded description label url { path } entity { parent ... on MenuLinkContent { linkedEntity(language_fallback: true, bypass_access_check: true) { ... on Node { entityPublished moderationState } } } } } query vaGovFooterQuery { menuByName(name: "va-gov-footer") { name description links { ...MenuItem links { ...MenuItem } } } } ``` This results in the following data: ```json { "data": { "menuByName": { "name": "VA.gov Footer", "description": "Displayed in VA.gov global & injected footer; 3-column list of footer links.", "links": [ { "expanded": true, "description": "Column 1", "label": "Column 1", "url": { "path": "" }, "entity": { "parent": null, "linkedEntity": null }, "links": [ { "expanded": false, "description": "Homeless Veterans", "label": "Homeless Veterans", "url": { "path": "https://www.va.gov/homeless/" }, "entity": { "parent": null, "linkedEntity": null } }, { "expanded": false, "description": "Women Veterans", "label": "Women Veterans", "url": { "path": "https://www.va.gov/womenvet/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "Minority Veterans", "label": "Minority Veterans", "url": { "path": "https://www.va.gov/centerforminorityveterans/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "LGBTQ+ Veterans", "label": "LGBTQ+ Veterans", "url": { "path": "https://www.patientcare.va.gov/lgbt/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "PTSD", "label": "PTSD", "url": { "path": "https://www.ptsd.va.gov/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "Mental health", "label": "Mental health", "url": { "path": "https://www.mentalhealth.va.gov/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "Adaptive sports and special events", "label": "Adaptive sports and special events", "url": { "path": "https://www.va.gov/adaptivesports/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } }, { "expanded": false, "description": "VA Outreach Events", "label": "VA Outreach Events", "url": { "path": "/outreach-and-events" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": { "entityPublished": true, "moderationState": "published" } } }, { "expanded": false, "description": "National Resource Directory", "label": "National Resource Directory", "url": { "path": "https://www.nrd.gov/" }, "entity": { "parent": "menu_link_content:1dd6f786-2c07-4d41-9c66-8676dd467f2d", "linkedEntity": null } } ] }, { "expanded": false, "description": "Column 2", "label": "Column 2", "url": { "path": "" }, "entity": { "parent": null, "linkedEntity": null }, "links": [ { "expanded": false, "description": "VA forms", "label": "VA forms", "url": { "path": "/find-forms" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": { "entityPublished": true, "moderationState": "published" } } }, { "expanded": false, "description": "VA health care access and quality", "label": "VA health care access and quality", "url": { "path": "https://www.accesstocare.va.gov" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "Accredited claims representative", "label": "Accredited claims representative", "url": { "path": "https://www.va.gov/ogc/accreditation.asp" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "VA mobile apps", "label": "VA mobile apps", "url": { "path": "https://www.mobile.va.gov/appstore/" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "State Veterans Affairs offices", "label": "State Veterans Affairs offices", "url": { "path": "https://www.va.gov/statedva.htm" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "Doing business with VA", "label": "Doing business with VA", "url": { "path": "https://www.va.gov/landing2_business.htm" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "Careers at VA", "label": "Careers at VA", "url": { "path": "https://www.va.gov/jobs/" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": null } }, { "expanded": false, "description": "VA outreach materials", "label": "VA outreach materials", "url": { "path": "/outreach-and-events/outreach-materials" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": { "entityPublished": true, "moderationState": "published" } } }, { "expanded": false, "description": "Your VA welcome kit", "label": "Your VA welcome kit", "url": { "path": "/welcome-kit" }, "entity": { "parent": "menu_link_content:c2a7f6fb-5a3e-4ce8-9867-f442bb8ddee2", "linkedEntity": { "entityPublished": true, "moderationState": "published" } } } ] }, { "expanded": false, "description": "Column 3", "label": "Column 3", "url": { "path": "" }, "entity": { "parent": null, "linkedEntity": null }, "links": [ { "expanded": false, "description": "VA news", "label": "VA news", "url": { "path": "https://www.news.va.org" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Press releases", "label": "Press releases", "url": { "path": "https://www.va.gov/opa/pressrel/" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Email updates", "label": "Email updates", "url": { "path": "https://public.govdelivery.com/accounts/USVA/subscriber/new/" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Facebook", "label": "Facebook", "url": { "path": "https://www.facebook.com/VeteransAffairs" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Instagram", "label": "Instagram", "url": { "path": "https://www.instagram.com/deptvetaffairs/" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Twitter", "label": "Twitter", "url": { "path": "https://www.twitter.com/DeptVetAffairs/" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "Flickr", "label": "Flickr", "url": { "path": "https://www.flickr.com/photos/VeteransAffairs/" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "YouTube", "label": "YouTube", "url": { "path": "https://www.youtube.com/user/DeptVetAffairs" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } }, { "expanded": false, "description": "All VA social media", "label": "All VA social media", "url": { "path": "https://www.va.gov/opa/socialmedia.asp" }, "entity": { "parent": "menu_link_content:e9a8e00a-e6da-4ff8-9062-4a5a42c93346", "linkedEntity": null } } ] } ] } } } ```
GraphQL and example data for Footer Bottom rail menu: ```graphql fragment MenuItem on MenuLink { expanded description label url { path } entity { parent ... on MenuLinkContent { linkedEntity(language_fallback: true, bypass_access_check: true) { ... on Node { entityPublished moderationState } } } } } query vaGovFooterBottomRailQuery { menuByName(name: "footer-bottom-rail") { name description links { ...MenuItem links { ...MenuItem } } } } ``` This results in the data: ```json { "data": { "menuByName": { "name": "VA.gov Footer bottom rail", "description": "Displayed in VA.gov global & injected footer; horizontal list of links at the bottom of the page.", "links": [ { "expanded": false, "description": "Accessibility", "label": "Accessibility", "url": { "path": "https://www.va.gov/accessibility-at-va" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Civil Rights", "label": "Civil Rights", "url": { "path": "https://www.va.gov/resources/your-civil-rights-and-how-to-file-a-discrimination-complaint/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Freedom of Information Act (FOIA)", "label": "Freedom of Information Act (FOIA)", "url": { "path": "https://www.va.gov/foia/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Office of Inspector General", "label": "Office of Inspector General", "url": { "path": "https://www.va.gov/oig/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Plain language", "label": "Plain language", "url": { "path": "https://www.va.gov/opa/Plain_Language.asp" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Privacy, policies, and legal information", "label": "Privacy, policies, and legal information", "url": { "path": "https://www.va.gov/privacy-policy/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "VA Privacy Service", "label": "VA Privacy Service", "url": { "path": "https://www.va.gov/privacy/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "No FEAR Act Data", "label": "No FEAR Act Data", "url": { "path": "https://www.va.gov/ormdi/NOFEAR_Select.asp" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "USA.gov", "label": "USA.gov", "url": { "path": "https://www.usa.gov/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "VA performance dashboard", "label": "VA performance dashboard", "url": { "path": "https://www.va.gov/performance-dashboard/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] }, { "expanded": false, "description": "Veterans Portrait Project", "label": "Veterans Portrait Project", "url": { "path": "https://www.va.gov/veterans-portrait-project/" }, "entity": { "parent": null, "linkedEntity": null }, "links": [] } ] } } } ```

Drupal data, Design / Usage considerations

Marked up screenshot ![footer Drupal implementation](https://user-images.githubusercontent.com/85581471/205992057-3c66ce0a-eac2-460a-9abd-70ccbc7cbcf2.png)

Mural

This mural breaks down how footer columns correspond between mobile and desktop, and corresponding Drupal menus.

Behavior notes

  1. The top 4th column behaves very differently on mobile - Footer mobile vs. desktop behavior - Mural. We are not modifying this column with Drupalizing changes, so behavior should be preserved as-is.
  2. At build time, the existing footer json is pulled into a generated file, headerfooter.json, that is used for header & footer injection. All testing must include injected scenarios so we don't regress injected sites.

Notes from 10512

These notes summarize SPIKE findings, notably: we will use React.

"Language Assistance" Links

This section of the footer menu involves a React component where links are generated from some configuration around supported languages. Clicks on these links have effects that seem to extend outside the footer alone, so has some hidden complexity. Drupalizing this menu is not in scope of this ticket, and a SPIKE to assess effort / implications is ticketed separately: #11492

Acceptance Criteria

All layouts:

Mobile:

Injection:

Reviews:

CMS Team

Please check the team(s) that will do this work.

wesrowe commented 1 year ago

@ryguyk, FYI, Dave Conlon agreed to our strategy of leaving Column 4 (contact stuff) out of this ticket.

Regarding rel, referrer and target attributes, did we decide that this can be handled automatically by the FE? E.g., I recall someone noticing that only links outside the modernized VAgov experience open in a new tab.

ryguyk commented 1 year ago

I'm not sure that things are consistent enough to handle this via patterns. The two settings below together provide a good example of variability that could not be accounted for. Specifically, both are VA subdomains, but only one is set with target="_blank".

  {
    "column": 1,
    "href": "https://www.mentalhealth.va.gov",
    "order": 6,
    "target": "",
    "title": "Mental health",
    "rel": "noopener noreferrer"
  },
  {
    "column": 2,
    "href": "https://www.accesstocare.va.gov/",
    "order": 2,
    "target": "_blank",
    "title": "VA health care access and quality",
    "rel": "noopener noreferrer"
  },

It's a separate but useful question to ask whether we know the current behavior is correct/required. Might it be the case, for example, that the two above should behave the same? If so, we could possibly handle this via those redefined rules.

wesrowe commented 1 year ago

@ryguyk, so that's target. Can rel be inferred?

wesrowe commented 1 year ago

Also, I pinged the SW Content channel about whether there is existing guidance on targets/new-tab.

wesrowe commented 1 year ago

DST helped me find the Behavior section on Link docs. These links should all open in the same tab. So no need for target differences! 🎉

ryguyk commented 1 year ago

@wesrowe re: "Can rel be inferred?"

See this Slack thread.

wesrowe commented 1 year ago

Per @ryguyk (slack link above), noopener is not applicable when opening in the same tab.

swirtSJW commented 1 year ago

We need to make sure that the query is updated to take into account both the "enabled" property of the menu item AND the published status of the node it is pointing to. (If a node is in the menu, but the page is not published, it should not be rendered in the menu.)

wesrowe commented 1 year ago

@swirtSJW, I added as an AC

jilladams commented 1 year ago

@chri5tia a hidden task in this sprint is to support Josh on implementing the front-end of the menus. I'm adding notes to ticket body for what I know is true today, but please add any notes that may be relevant re: GraphQL he'll need.

dsasser commented 1 year ago

We need to make sure that the query is updated to take into account both the "enabled" property of the menu item AND the published status of the node it is pointing to. (If a node is in the menu, but the page is not published, it should not be rendered in the menu.)

@swirtSJW I'm helping out here with crafting the graphql for the FE. I tested a query very close to that used in the megamenu, but it produces unpublished links. Any idea how we are dealing with this for the megamenu, for instance?

Here is my query for the footer today:

query vaGovFooter {
  menuLinkContentQuery(
    limit: 5000
    sort: {field: "weight", direction: ASC}
    filter: {conditions: [{field: "enabled", value: "1"}, {field: "menu_name", value: "va-gov-footer"}]}
  ) {
    entities {
      entityId
      entityLabel
      ... on MenuLinkContentVaGovFooter {
        menuName
        parent
        weight
        title
        uuid
        link {
          url {
            path
          }
        }
      }
    }
  }
}

This is producing a result which contains an unpublished node. (though I am logged in as an admin). Do we know what role the authenticated content-build originated queries use? Scratch that, I figured out that the query made during content-build will also include unpublished pages. User 'content_build_api' has the role Content API Consumer which as the View any unpublished content permission. I have verified that by removing this permission, the unpublished node is not returned with the above query during content-build, so at least I know that access checks are being performed correctly on graphql menu queries. That said, I'm still not sure where to poke at to override this on an individual query basis.

I could pour through the graphql module documentation and code to figure this out, but was hoping you would have a place to point me to save some time.

dsasser commented 1 year ago

@swirtSJW I confirmed that at least the megamenu (and ostensibly, all menuLinkContent queries) return unpublished nodes. I don't see any FE code that is pulling out unpublished nodes after queries have been run, but perhaps I'm missing something.

Would you agree that we could move the requirement to 'only return published nodes' to its own issue for pointing/refinement? This seems wider spread than just the footer.

jilladams commented 1 year ago

I vote in favor of this. It'll make for clearer code review to separate the bigger picture from the smaller picture here. If you'd like me to cut that ticket, holler.

dsasser commented 1 year ago

We need to make sure that the query is updated to take into account both the "enabled" property of the menu item AND the published status of the node it is pointing to. (If a node is in the menu, but the page is not published, it should not be rendered in the menu.)

I originally understood this to mean that Drupal/GraphQl should be filtering out the unpublished nodes, such that the FE would be receiving only published nodes in the response JSON. However, I recently learned that we have an established pattern of filtering unpublished nodes/entities on the FE. This is explained here.

Therefore, we need to: 1) Rework the GraphQL query to provide the node's published status. This has been ticketed separately here. 2) Ensure that the footer menu template is filtering for published items using the method described in the like above.

jilladams commented 1 year ago

Per sprint planning: let's stop adding to stretch, so it's not a topic in plannings, until it's actually high enough priority to get it done. Saves some conversation about it.

Thusly: moving to top of Refined.

jilladams commented 1 year ago

From planning: We started this opportunistically in S83, but in S84 we are now able to begin frontend for Income Limits, which is the higher priority. The remaining work here is to get adequately tested in order to ship. It may be advantageous to use a feature flag to get this all staged somewhere -- that would require using CMS feature flags, not flipper/feature flags.

Partial cause of churn: Github has had degraded operations for checks on PRs, slowing down preview builds for verification. If Github stabilizes, we may not need feature flags.

randimays commented 1 year ago

This has been validated in production in desktop, mobile and injected footer scenarios. We're good to go! 🎉