Skip to content

Commit

Permalink
Feature: IPM Launch homepage updates (#1065)
Browse files Browse the repository at this point in the history
* Add IPM static section to homepage

* Add Products to navbar

* Update spec expectations for static content

* DM-5254: Add Products to homepage search dropdown (#1066)

* Add Products to homepage dropdown init state

* Dynamically filter products

* Truncate results to 2 per type and update spec expectations

* Move Product above Tags

* Update type assignment for ahoy events

* Update more test expectations

* Fix up spec expectations

* Update data properties for product

---------

Co-authored-by: Camille Villa <[email protected]>

* Fix code scanning alert no. 344: DOM text reinterpreted as HTML

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Conditionally render Intrapreneur section

---------

Co-authored-by: Camille Villa <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent 3caadc3 commit a1762bb
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 43 deletions.
Binary file added app/assets/images/homepage/section-ipm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 37 additions & 10 deletions app/assets/javascripts/homepage_search_dropdown.es6
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ function setupSearchDropdown() {
const dropdown = $('#search-dropdown');

const allCategories = JSON.parse($('.homepage-search').attr('data-categories') || '[]');
const mostPopularCategories = allCategories.slice(0, 3);
const mostPopularCategories = allCategories.slice(0, 2);

const allInnovations = JSON.parse($('.homepage-search').attr('data-innovations') || '[]');
const mostRecentInnovations = allInnovations.slice(0, 3);
const mostRecentInnovations = allInnovations.slice(0, 2);

const allCommunities = JSON.parse($('.homepage-search').attr('data-communities') || '[]');
const mostPopularCommunities = allCommunities.slice(0, 3);
const mostPopularCommunities = allCommunities.slice(0, 2);

const allProducts = JSON.parse($('.homepage-search').attr('data-products') || '[]');
const mostRecentProducts = allProducts.slice(0, 2);

searchInput.focus(function() {
dropdown.show();
Expand All @@ -18,10 +21,11 @@ function setupSearchDropdown() {

searchInput.on('input', function() {
let searchTerm = searchInput.val().toLowerCase();
let filteredCategories = searchTerm ? allCategories.filter(category => category.name.toLowerCase().includes(searchTerm)).slice(0,3) : mostPopularCategories;
let filteredInnovations = searchTerm ? allInnovations.filter(innovation => innovation.name.toLowerCase().includes(searchTerm)).slice(0,3) : mostRecentInnovations;
let filteredCommunities = searchTerm ? allCommunities.filter(community => community.name.toLowerCase().includes(searchTerm)).slice(0,3) : mostPopularCommunities;
updateDropdown(filteredCategories, filteredInnovations, filteredCommunities);
let filteredCategories = searchTerm ? allCategories.filter(category => category.name.toLowerCase().includes(searchTerm)).slice(0,2) : mostPopularCategories;
let filteredInnovations = searchTerm ? allInnovations.filter(innovation => innovation.name.toLowerCase().includes(searchTerm)).slice(0,2) : mostRecentInnovations;
let filteredCommunities = searchTerm ? allCommunities.filter(community => community.name.toLowerCase().includes(searchTerm)).slice(0,2) : mostPopularCommunities;
let filteredProducts = searchTerm ? allProducts.filter(product => product.name.toLowerCase().includes(searchTerm)).slice(0,2) : mostRecentProducts;
updateDropdown(filteredCategories, filteredInnovations, filteredCommunities, filteredProducts);
});

$(document).keydown(function(e) {
Expand Down Expand Up @@ -56,8 +60,8 @@ function setupSearchDropdown() {
});
}

function updateDropdown(categories, innovations, communities) {
$('#category-list, #practice-list, #community-list').empty();
function updateDropdown(categories, innovations, communities, products) {
$('#category-list, #practice-list, #community-list, #product-list').empty();

categories.forEach(function(category) {
let link = $('<a></a>')
Expand Down Expand Up @@ -94,6 +98,18 @@ function updateDropdown(categories, innovations, communities) {

$('#community-list').append(listItem);
});

products.forEach(function(product) {
let link = $('<a></a>')
.attr('href', `/products/${encodeURIComponent(product.slug)}`)
.text(product.name);
let listItem = $('<li></li>')
.addClass('search-result')
.attr('data-product-id', product.id)
.append(link);

$('#product-list').append(listItem);
});
}

function setupClickTracking(listSelector, eventName, dataAttribute) {
Expand All @@ -110,7 +126,17 @@ function setupClickTracking(listSelector, eventName, dataAttribute) {
const id = e.target.closest('.search-result').getAttribute(dataAttribute);

let properties = { from_homepage: true};
properties[dataAttribute === 'data-practice-id' ? 'practice_name' : 'category_name'] = name;
switch(dataAttribute) {
case 'data-practice-id':
properties['practice_name'] = name;
break;
case 'data-product-id':
properties['product_name'] = name;
break;
default: // tags and communities-as-tags
properties['category_name'] = name;
}

properties[dataAttribute.slice(5)] = parseInt(id); // Removes 'data-' and uses the rest as the key

ahoy.track(eventName, properties);
Expand Down Expand Up @@ -149,5 +175,6 @@ addEventListener('turbolinks:load', function () {
setupClickTracking('#practice-list', "Dropdown Practice Link Clicked", 'data-practice-id');
setupClickTracking('#category-list', "Category selected", 'data-category_id');
setupClickTracking('#community-list', "Category selected", 'data-category_id');
setupClickTracking('#product-list', "Dropdown Product Link Clicked", 'data-product-id');
}
});
8 changes: 5 additions & 3 deletions app/assets/stylesheets/dm/pages/_home.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@

#category-list,
#practice-list,
#community-list {
#community-list,
#product-list
{
a {
text-decoration: none;
color: color($theme-color-base-ink);
Expand Down Expand Up @@ -106,8 +108,8 @@
}
}

.homepage-section, #feature-nominate-innovation {
#submit-innovation-title {
.homepage-section, #feature-nominate-innovation, #ipm-section {
#submit-innovation-title, #ipm-section-title {
@include at-media(tablet) {
@include u-margin-top(0);
}
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def index
@dropdown_categories = get_categories_by_popularity
@dropdown_communities = get_categories_by_popularity(true)
@dropdown_practices, @practice_names = get_dropdown_practices
@dropdown_products, @product_names = get_dropdown_products
@homepage = Homepage.where(published: true)&.first
if @homepage
current_features = @homepage&.homepage_features
Expand Down Expand Up @@ -143,6 +144,17 @@ def dropdown_practices
scope
end

def get_dropdown_products
product_names = []
dropdown_products = Product.where(published: true, retired: false).order("created_at DESC")
products_hash = dropdown_products.pluck(:id, :name, :slug).map do |id, name, slug|
product_names << name
{name: name, id: id, slug: slug }
end

return products_hash, product_names
end

def get_diffusion_histories(is_public_practice)
DiffusionHistory.get_with_practices(is_public_practice).order(Arel.sql("lower(practices.name)"))
end
Expand Down
34 changes: 28 additions & 6 deletions app/views/home/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
data-categories="<%= @dropdown_categories.to_json %>"
data-innovations="<%= @dropdown_practices.to_json %>"
data-communities="<%= @dropdown_communities.to_json %>"
data-products="<%= @dropdown_products.to_json %>"
>
<form id="dm-homepage-search-form" class="usa-search margin-bottom-2" role="search" aria-label="Search innovations and categories">
<div class="dm-homepage-search-container width-full">
Expand All @@ -44,36 +45,45 @@
style="display: none;"
>

<% if @dropdown_practices.present? && @dropdown_categories.present? && @dropdown_communities.present? %>
<% if @dropdown_practices.present? && @dropdown_categories.present? && @dropdown_communities.present? && @dropdown_products.present? %>
<div class="dropdown-content margin-4">
<div class="result-section" data-type="innovation">
<h3 class="search-header usa-prose-h3 margin-bottom-0">Innovations</h3>
<ul id="practice-list" class="usa-list usa-list--unstyled padding-bottom-1 padding-top-1 display-block">
<% @dropdown_practices[0..2].each do |practice| %>
<% @dropdown_practices[0..1].each do |practice| %>
<li class="search-result" data-practice-id=<%= practice[:id] %>>
<a href="/innovations/<%= practice[:slug] %>"><%= practice[:name] %></a>
</li>
<% end %>
</ul>
<a class="browse-all-link text-bold" href="<%= search_path %>">Browse all Innovations</a>
</div>

<div class="result-section" data-type="product">
<h3 class="search-header usa-prose-h3 margin-bottom-0">Products</h3>
<ul id="product-list" class="usa-list usa-list--unstyled padding-bottom-1 padding-top-1 display-block">
<% @dropdown_products[0..1].each do |product| %>
<li class="search-result" data-product-id=<%= product[:id]%>>
<a href=<%= "/products/#{product[:slug]}" %>><%= product[:name] %></a>
</li>
<% end %>
</ul>
<a class="browse-all-link text-bold" href="/intrapreneurial-product-marketplace">Browse all Products</a>
</div>
<div class="result-section" data-type="category">
<h3 class="search-header usa-prose-h3 margin-bottom-0" aria-label="Tags">Tags</h3>
<ul id="category-list" class="usa-list usa-list--unstyled padding-bottom-1 padding-top-1 display-block">
<% @dropdown_categories[0..2].each do |category| %>
<% @dropdown_categories[0..1].each do |category| %>
<li class="search-result" data-category_id=<%= category[:id] %>>
<a href=<%= "/search?category=#{URI.encode_www_form_component(category[:name])}" %>><%= category[:name] %></a>
</li>
<% end %>
</ul>
<a class="browse-all-link text-bold" href="<%= search_path %>">Browse all Tags</a>
</div>

<div class="result-section" data-type="community">
<h3 class="search-header usa-prose-h3 margin-bottom-0">Communities</h3>
<ul id="community-list" class="usa-list usa-list--unstyled padding-bottom-1 padding-top-1 display-block">
<% @dropdown_communities[0..2].each do |community| %>
<% @dropdown_communities[0..1].each do |community| %>
<li class="search-result" data-category_id=<%= community[:id] %>>
<a href=<%= "/search?category=#{URI.encode_www_form_component(community[:name])}" %>><%= community[:name] %></a>
</li>
Expand Down Expand Up @@ -136,6 +146,18 @@
</div>
<% end %>
</section>
<section id="ipm-section" class="">
<div class="grid-row grid-gap">
<%= image_tag("homepage/section-ipm.png", alt: "a doctor and medical staff speaking in a busy hallway", class: "margin-bottom-3 grid-col-12 tablet:grid-col-6 desktop:grid-col-6") %>
<div class="margin-bottom-3 grid-col-12 tablet:grid-col-6 desktop:grid-col-6">
<h2 id="ipm-section-title" class="usa-prose-h2 margin-bottom-3 tablet:margin-bottom-5 desktop:margin-bottom-5">Intrapreneurial Products</h2>
<p class="usa-prose-body margin-bottom-5">
The VA Intrapreneurial Product Marketplace highlights frontline VA employee innovators and their products available for purchase to put back in the hands of who they were designed with and for, Veterans.
</p>
<%= link_to 'Learn More', '/intrapreneurial-product-marketplace', class: 'usa-link', 'aria-describedby': 'ipm-section-title', data: { turbolinks: false } %>
</div>
</div>
</section>
<section id="homepage-section-2" class="homepage-section">
<% if fallback_content %>
<h2 class="section-title">Trending Tags</h2>
Expand Down
27 changes: 18 additions & 9 deletions app/views/products/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,25 @@
<% end %>
</section>
<section id="practice-show-intrapreneur" class="grid-container">
<h2 id="intrapreneur" class="sidenav-header">Intrapreneur</h2>
<h3><%= 'Innovator'.pluralize(@product.va_employee_practices.count) %></h3>
<% @product.va_employee_practices.each do |innovator| %>
<div class="innovators margin-bottom-1">
<p><%= innovator.va_employee&.name %></p>
<p class="text-italic"><%= innovator.va_employee&.role %></p>
</div>
<% innovators = @product.va_employees
from_the_innovator = @product.origin_story
%>
<% unless innovators.blank? && from_the_innovator.blank? %>
<h2 id="intrapreneur" class="sidenav-header">Intrapreneur</h2>
<% unless innovators.blank? %>
<h3><%= 'Innovator'.pluralize(innovators.count) %></h3>
<% innovators.each do |innovator| %>
<div class="innovators margin-bottom-1">
<p><%= innovator&.name %></p>
<p class="text-italic"><%= innovator&.role %></p>
</div>
<% end %>
<% end %>
<% unless from_the_innovator.blank? %>
<h3>From the Innovator</h3>
<div class="origin-story"><%= simple_format(@product.origin_story, wrapper_tag: "p") %></div>
<% end %>
<% end %>
<h3>From the Innovator</h3>
<div class="origin-story"><%= simple_format(@product.origin_story, wrapper_tag: "p") %></div>
</section>
<% if @product.practice_multimedia.any? %>
<section id="practice-show-multimedia" class="margin-bottom-5 grid-container">
Expand Down
5 changes: 5 additions & 0 deletions app/views/shared/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
<% end %>
</ul>
</li>
<li class="usa-nav__primary-item border-base-light">
<a class="margin-y-2px desktop:margin-y-0 usa-nav__link <%= 'usa-current' if request.fullpath == '/intrapreneurial-product-marketplace' %>" href="/intrapreneurial-product-marketplace">
<span class="text-normal desktop:text-semibold">Products</span>
</a>
</li>
</ul>
<div class="usa-nav__secondary desktop:display-none">
<%= render partial: 'shared/header_user_login_nav', locals: { accordion_id: 'dm-user-mobile-profile-options' } %>
Expand Down
65 changes: 50 additions & 15 deletions spec/features/homepage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
@practice_2 = create(:practice, name: 'The Second Best Practice Ever!', initiating_facility_type: 'facility', tagline: 'Test tagline', date_initiated: 'Sun, 06 Feb 1992 00:00:00 UTC +00:00', created_at: 'Sun, 06 Feb 1992 00:00:00 UTC +00:00', summary: 'This is the best practice ever.', overview_problem: 'overview-problem', is_public: true, published: true, approved: true, user: @user)
@practice_3 = create(:practice, name: 'The Third Best Practice Ever!', initiating_facility_type: 'facility', tagline: 'Test tagline', date_initiated: 'Sun, 07 Feb 1992 00:00:00 UTC +00:00', created_at: 'Sun, 07 Feb 1992 00:00:00 UTC +00:00', summary: 'This is the best practice ever.', overview_problem: 'overview-problem', is_public: true, published: true, approved: true, user: @user)
@va_only_practice = create(:practice, name: 'Recent VA-only practice!', initiating_facility_type: 'facility', tagline: 'Test tagline', date_initiated: 'Sun, 08 Feb 1992 00:00:00 UTC +00:00', created_at: 'Sun, 07 Feb 1992 00:00:00 UTC +00:00', summary: 'This is the best practice ever.', overview_problem: 'overview-problem', is_public: false, published: true, approved: true, user: @user)
@product_1 = create(:product, published: true)
@product_2 = create(:product , published: true)
@product_3 = create(:product, published: true)
@ipm = create(:page_group, name: "Intrapreneurial Product Marketplace")
@ipm_homepage = create(:page, page_group: @ipm, slug: "home", title: "Intrapreneurial Product Marketplace" )

create(:practice_origin_facility, practice: @practice, facility_type: 0, va_facility_id: 1)

Expand All @@ -37,8 +42,20 @@
visit '/'
end

it 'links to the Shark Tank page' do
expect(page).to have_link(href: '/competitions/shark-tank')
describe 'static content sections' do
it 'invites users to submit innovations' do
within('#main-content') do
expect(page).to have_content('Submit Innovations')
expect(page).to have_link('Nominate', href: nominate_an_innovation_path)
end
end

it 'features Intrapreneurial Products' do
within('#main-content') do
expect(page).to have_content('Intrapreneurial Products')
expect(page).to have_link('Learn More', href: '/intrapreneurial-product-marketplace')
end
end
end

describe 'search section' do
Expand All @@ -54,13 +71,6 @@
end
end

it 'invites users to submit innovations' do
within('#main-content') do
expect(page).to have_content('Submit Innovations')
expect(page).to have_link('Nominate', href: nominate_an_innovation_path)
end
end

it 'allows the user to subscribe to the DM newsletter by taking them to the GovDelivery site' do
fill_in('Your email address', with: '[email protected]')

Expand Down Expand Up @@ -88,7 +98,6 @@

it 'lists most recently created innovations' do
within '#practice-list' do
expect(page).to have_content('The Best Practice Ever!')
expect(page).to have_content('The Second Best Practice Ever!')
expect(page).to have_content('The Third Best Practice Ever!')

Expand All @@ -103,14 +112,13 @@
find('#dm-homepage-search-field').click

expect(page).to have_content('Recent VA-only practice!')
expect(page).to have_content('The Second Best Practice Ever!')
expect(page).to have_content('The Third Best Practice Ever!')
end

it 'lists popular categories' do
within '#category-list' do
expect(page).to have_content('COVID')
expect(page).to have_content('Telehealth')
expect(page).to have_content('Nutrition & Food')
end
end

Expand Down Expand Up @@ -150,10 +158,22 @@
expect(page).to have_content("1 Result:")
expect(page).to have_content(@practice_3.name)
end

it 'temporarily links to IPM pagebuilder page' do
within '#search-dropdown' do
expect(page).to have_link('Browse all Products', href: '/intrapreneurial-product-marketplace')
end
end

it 'lists most recently created products' do
expect(page).to have_content(@product_3.name)
expect(page).to have_content(@product_2.name)
expect(page).not_to have_content(@product_1.name)
end
end

it 'lets a user navigate results with arrow keys' do
page.send_keys :down, :down, :down, :down, :down # navigate to first category
7.times { page.send_keys :down} # navigate to first category
page.send_keys :enter # select category
expect(page).to have_current_path('/search?category=COVID')
expect(page).to have_content("2 Results: TAG: COVID")
Expand All @@ -166,10 +186,10 @@
end

it 'tracks clicks on practice links' do
event = wait_for_ahoy_js('The Best Practice Ever!')
event = wait_for_ahoy_js(@practice_2.name)

expect(event.name).to eq("Dropdown Practice Link Clicked")
expect(event.properties["practice_name"]).to eq("The Best Practice Ever!")
expect(event.properties["practice_name"]).to eq(@practice_2.name)
expect(event.properties["from_homepage"]).to be_truthy
end

Expand Down Expand Up @@ -209,6 +229,21 @@
expect(event.name).to eq("Dropdown Browse-all Link Clicked")
expect(event.properties["type"]).to eq("community")
end

it 'tracks clicks on product links' do
event = wait_for_ahoy_js(@product_2.name)

expect(event.name).to eq("Dropdown Product Link Clicked")
expect(event.properties["product_name"]).to eq(@product_2.name)
expect(event.properties["from_homepage"]).to be_truthy
end

it 'tracks clicks on "Browse All Products Link' do
event = wait_for_ahoy_js('Browse all Products')

expect(event.name).to eq("Dropdown Browse-all Link Clicked")
expect(event.properties["type"]).to eq("product")
end
end

def wait_for_ahoy_js(link_text)
Expand Down
Loading

0 comments on commit a1762bb

Please sign in to comment.