diff --git a/.env.defaults b/.env.defaults index fbdaddd563..6df298f019 100644 --- a/.env.defaults +++ b/.env.defaults @@ -78,5 +78,6 @@ STRAPI_WRITE_API_KEY="api_key_with_write_access" STRAPI_IMAGE_URL="http://strapi.teachcomputing.rpfdev.com" STRAPI_GRAPHQL_URL="http://strapi.teachcomputing.rpfdev.com/graphql" STRAPI_CONNECTION_TYPE="graphql" +STRAPI_TEST_SCHEMA_PATH="spec/support/cms/providers/strapi/schema.json" NODE_OPTIONS=--openssl-legacy-provider \ No newline at end of file diff --git a/app/components/cms/header_menu_component.rb b/app/components/cms/header_menu_component.rb new file mode 100644 index 0000000000..b07872e577 --- /dev/null +++ b/app/components/cms/header_menu_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Cms::HeaderMenuComponent < ViewComponent::Base + def initialize(menu_items:) + @menu_items = menu_items + end +end diff --git a/app/components/cms/header_menu_component/header_menu_component.html.erb b/app/components/cms/header_menu_component/header_menu_component.html.erb new file mode 100644 index 0000000000..9dcc976976 --- /dev/null +++ b/app/components/cms/header_menu_component/header_menu_component.html.erb @@ -0,0 +1,15 @@ +
\ No newline at end of file diff --git a/app/components/cms/header_menu_component/header_menu_component.scss b/app/components/cms/header_menu_component/header_menu_component.scss new file mode 100644 index 0000000000..d6ea5a80f8 --- /dev/null +++ b/app/components/cms/header_menu_component/header_menu_component.scss @@ -0,0 +1,170 @@ +.cms-header-menu { + list-style: none; + margin: 0; + padding: 0; + + li { + @include govuk-media-query($from: desktop) { + position: relative; + } + } + + ul li ul li { + clear: both; + width: 100%; + } + + @include govuk-media-query($from: desktop) { + display: flex; + flex-direction: row; + margin: 0; + padding-top: 25px 0 0 0; + } + + .govuk-body { + margin-bottom: 0 !important; + + @include govuk-media-query($from: desktop) { + margin-bottom: unset; + } + } + + @media (any-hover: hover) { + .cms-header-menu__item:hover .dropdown__expander-content, .cms-header-menu__item.dropdown__expander { + display: block; + opacity: 1; + visibility: visible; + } + } + + &__wrap { + display: flex; + padding: 5px 0; + + @include govuk-media-query($from: tablet) { + padding: 7px 0; + } + + @include govuk-media-query($from: desktop) { + padding: 0; + } + } + + &__item { + border-bottom: 1px solid #ced0d2; + clear: both; + flex-grow: 1; + font-weight: 700; + margin-bottom: 0; + padding-top: 0.5rem; + position: static; + z-index: 2; + + &:first-child { + background-image: none; + border-top: 1px solid #ced0d2; + } + + &:hover { + background-image: none; + } + + @include govuk-media-query($until: desktop) { + outline: none; + } + + @include govuk-media-query($from: desktop) { + background-image: url('../images/icons/line.svg'); + background-position: left center; + background-repeat: no-repeat; + background-size: 2px 20px; + border: none; + min-width: 7rem; + padding: 1rem 0 1rem 0.9rem; + + &:first-child { + border-top: none; + } + } + + .govuk-header__link { + @include govuk-media-query($from: desktop) { + font-size: 19px; + font-weight: bold !important; + } + } + + &[aria-expanded='true'] .cms-header-menu__item-icon { + background-image: url('../images/icons/arrow-down-purple.svg'); + @include govuk-media-query($from: desktop) { + background-position: center 9px; + } + } + + &-text, + &-text:hover, &-text:focus { + color: $white; + display: inline-block; + font-size: 1.125rem; + width: 100%; + padding-bottom: 0.5rem; + padding-left: 2px; + + @include govuk-media-query($from: desktop) { + font-size: 1.1875rem; + width: auto; + padding-bottom: 0; + padding-left: unset; + } + } + + + &-icon { + background-image: url('../images/icons/tick-white-no-border.svg'); + background-position: 0 10px; + background-repeat: no-repeat; + background-size: 0.7rem; + display: block; + width: 24px; + + @include govuk-media-query($from: desktop) { + background-position: center 10px; + padding-left: 10px; + } + } + } + + @media (any-hover: hover) { + .cms-header-menu__item:hover { + background-color: $white; + color: $purple-dark; + border: none; + margin: 0; + + @include govuk-media-query($from: desktop) { + filter: drop-shadow(0px 3px 10px rgba(0, 0, 0, 0.3)); + background-image: none; + } + } + + .cms-header-menu__item:hover .cms-header-menu__item-text { + color: $purple-dark; + padding-bottom: 0.5rem; + padding-left: 2px; + + @include govuk-media-query($from: desktop) { + padding-bottom: 0; + padding-left: unset; + } + } + } + + @media (any-hover: hover) { + .cms-header-menu__item:hover .cms-header-menu__item-icon { + background-image: url('../images/icons/arrow-down-purple.svg'); + @include govuk-media-query($from: desktop) { + background-position: center 9px; + } + } + } +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8635c9d735..356f68f652 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base include Pagy::Backend before_action :authenticate + before_action :access_cms_header def authenticate return unless ENV["BASIC_AUTH_PASSWORD"] @@ -13,6 +14,12 @@ def authenticate end end + def access_cms_header + @cms_header = Cms::Singles::Header.get + rescue ActiveRecord::RecordNotFound + @cms_header = nil + end + def authenticate_user! redirect_to(helpers.create_account_url) unless current_user end diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb deleted file mode 100644 index 8619140fd7..0000000000 --- a/app/helpers/navigation_helper.rb +++ /dev/null @@ -1,52 +0,0 @@ -module NavigationHelper - def header_navigation - [ - {text: "Primary", - children: [ - {text: "Subject lead toolkit", link: primary_teachers_path, label: "Subject lead toolkit"}, - {text: "Teacher certificate", link: cms_page_path("primary-certificate"), label: "Primary teacher certificate"}, - {text: "Enrichment", link: primary_enrichment_path, label: "Enrichment"}, - {text: "Early career teachers", link: cms_page_path("primary-early-careers"), label: "Primary ECT"}, - {text: "Trainee teachers", link: cms_page_path("primary-trainees"), label: "Primary Trainees"}, - {text: "Senior leaders", link: primary_senior_leaders_path, label: "Primary SLT"} - ]}, - {text: "Secondary", - children: [ - {text: "Teacher toolkit", link: secondary_teachers_path, label: "Secondary teachers"}, - {text: "Teacher certification", link: secondary_certification_path, label: "Secondary teachers certification"}, - {text: "Enrichment", link: secondary_enrichment_path, label: "Enrichment"}, - {text: "Early career teachers", link: cms_page_path("secondary-early-careers"), label: "Secondary ECT"}, - {text: "Trainee teachers", link: cms_page_path("secondary-trainees"), label: "Secondary Trainees"}, - {text: "Senior leaders", link: secondary_senior_leaders_path, label: "Secondary SLT"} - ].compact}, - {text: "Training and support", - children: [ - {text: "Courses", link: courses_path, label: "Courses"}, - {text: "Funding", link: cms_page_path("funding"), label: "Bursaries"}, - {text: "Computing Hubs", link: hubs_path, label: "Computing hubs"}, - {text: "I Belong programme", link: about_i_belong_path, label: "i-belong"}, - {text: "GCSE Computer Science support", link: cms_page_path("gcse-cs-support"), label: "GCSE support"}, - {text: "Computing Clusters", link: cms_page_path("computing-clusters"), label: "Computing clusters"}, - {text: "School Trusts", link: cms_page_path("school-trusts"), label: "School trusts"} - ]}, - {text: "Teaching resources", - children: [ - {text: "Teaching resources", link: curriculum_key_stages_path, label: "Teaching resources"}, - {text: "Isaac Computer Science", link: about_isaac_computer_science_path, label: "Isaac Computer Science"}, - {text: "Careers support", link: careers_support_path, label: "Careers support"}, - {text: "Secondary question banks", link: secondary_question_banks_path, label: "Teaching resources"}, - {text: "Artificial Intelligence", link: cms_page_path(page_slug: "artificial-intelligence"), label: "Artificial Intelligence"}, - {text: "Pedagogy", link: cms_page_path(page_slug: "pedagogy"), label: "Pedagogy"}, - {text: "Primary computing glossary", link: cms_page_path(page_slug: "primary-computing-glossary"), label: "Resources primary glossary"}, - {text: "Physical computing kits", link: cms_page_path("physical-computing-kit"), label: "Physical computing kits"} - ]}, - {text: "About us", - children: [ - {text: "About the NCCE", link: about_path, label: "About the NCCE"}, - {text: "News", link: cms_posts_path, label: "News"}, - {text: "Impact and evaluation", link: impact_path, label: "Impact"}, - {text: "Get involved", link: get_involved_path, label: "Get involved"} - ]} - ] - end -end diff --git a/app/services/cms/collections/programme.rb b/app/services/cms/collections/programme.rb index 6f570a8b83..19972c723b 100644 --- a/app/services/cms/collections/programme.rb +++ b/app/services/cms/collections/programme.rb @@ -1,10 +1,6 @@ module Cms module Collections class Programme < Resource - def to_search_record(index_time) - raise NotImplementedError - end - def self.is_collection = true def self.collection_attribute_mappings diff --git a/app/services/cms/models/header_menu.rb b/app/services/cms/models/header_menu.rb new file mode 100644 index 0000000000..2fffdb422a --- /dev/null +++ b/app/services/cms/models/header_menu.rb @@ -0,0 +1,13 @@ +module Cms + module Models + class HeaderMenu + def initialize(menus) + @menus = menus + end + + def render + Cms::HeaderMenuComponent.new(menu_items: @menus) + end + end + end +end diff --git a/app/services/cms/providers/strapi/factories/model_factory.rb b/app/services/cms/providers/strapi/factories/model_factory.rb index 80ba488b76..daac7fe47c 100644 --- a/app/services/cms/providers/strapi/factories/model_factory.rb +++ b/app/services/cms/providers/strapi/factories/model_factory.rb @@ -57,9 +57,22 @@ def self.process_model(mapping, all_data) model_class.new(cms_models: strapi_data.map { ComponentFactory.process_component(_1) }.compact) elsif model_class == Models::EnrichmentList to_enrichment_list(all_data, strapi_data) + elsif model_class == Models::HeaderMenu + to_menu(strapi_data) end end + def self.to_menu(strapi_data) + Models::HeaderMenu.new( + strapi_data.map do |menu_item| + { + label: menu_item[:label], + menu_items: menu_item[:menuItems].map { {label: _1[:label], url: _1[:url]} } + } + end + ) + end + def self.to_seo(strapi_data) Models::Seo.new( title: strapi_data[:title], diff --git a/app/services/cms/providers/strapi/graphql_client.rb b/app/services/cms/providers/strapi/graphql_client.rb index 2ca55c3ae4..b044e9392f 100644 --- a/app/services/cms/providers/strapi/graphql_client.rb +++ b/app/services/cms/providers/strapi/graphql_client.rb @@ -34,10 +34,15 @@ def one(resource_class, resource_id = nil, preview: false, preview_key: nil) data = clean_aliases(response.original_hash) results = data[:data][resource_class.graphql_key.to_sym][:data] - raise ActiveRecord::RecordNotFound if results.empty? - map_resource(resource_class, results.first, preview, preview_key) + record = if resource_class.is_collection + results.first + else + results + end + + map_resource(resource_class, record, preview, preview_key) end # This has been created to allow for alias to be alias_name__field_name diff --git a/app/services/cms/providers/strapi/graphql_connection.rb b/app/services/cms/providers/strapi/graphql_connection.rb index 320d985526..6b900730b9 100644 --- a/app/services/cms/providers/strapi/graphql_connection.rb +++ b/app/services/cms/providers/strapi/graphql_connection.rb @@ -17,7 +17,8 @@ def api(schema_path: nil) ) end - def dump_schema + def dump_schema(schema_path: nil) + api(schema_path:) # initialize client GraphQL::Client.dump_schema(@client.schema)&.to_json end end diff --git a/app/services/cms/providers/strapi/mocks/header.rb b/app/services/cms/providers/strapi/mocks/header.rb new file mode 100644 index 0000000000..28b6afecf4 --- /dev/null +++ b/app/services/cms/providers/strapi/mocks/header.rb @@ -0,0 +1,23 @@ +module Cms + module Providers + module Strapi + module Mocks + class Header < StrapiMock + attribute(:dropDowns) { Array.new(3) { DropDownMenu.generate_raw_data } } + end + + class DropDownMenu < StrapiMock + strapi_component "content-blocks.drop-down-menu" + attribute(:label) { Faker::Lorem.word } + attribute(:menuItems) { Array.new(5) { MenuItem.generate_raw_data } } + end + + class MenuItem < StrapiMock + strapi_component "content-blocks.menu-item" + attribute(:label) { Faker::Lorem.word } + attribute(:url) { "/primary-certificate" } + end + end + end + end +end diff --git a/app/services/cms/providers/strapi/queries/base_query.rb b/app/services/cms/providers/strapi/queries/base_query.rb index 6e5dd5cc1f..29dba61999 100644 --- a/app/services/cms/providers/strapi/queries/base_query.rb +++ b/app/services/cms/providers/strapi/queries/base_query.rb @@ -12,6 +12,7 @@ class BaseQuery Models::EnrichmentDynamicZone => EnrichmentDynamicZone, Models::EnrichmentList => EnrichmentList, Models::FeaturedImage => FeaturedImage, + Models::HeaderMenu => HeaderMenu, Models::PageTitle => PageTitle, Models::Seo => Seo, Models::SimpleTitle => SimpleField, @@ -78,14 +79,20 @@ def all_query(page, page_size, params = {}) GRAPHQL end - def single_query(id) + def single_query(id = nil) filters = {} - filters[resource_filter] = {eq: id} + if @collection_class.is_collection + raise StandardError if id.nil? + filters[resource_filter] = {eq: id} + end + filter_string = if filters.any? + "(#{query_string(:filters, filters)})" + end <<~GRAPHQL.freeze query { - #{resource_name}( #{query_string(:filters, filters)} ) { + #{resource_name} #{filter_string} { data { - id + #{"id" if @collection_class.is_collection} attributes { updatedAt createdAt diff --git a/app/services/cms/providers/strapi/queries/header_menu.rb b/app/services/cms/providers/strapi/queries/header_menu.rb new file mode 100644 index 0000000000..b8e9c721a4 --- /dev/null +++ b/app/services/cms/providers/strapi/queries/header_menu.rb @@ -0,0 +1,21 @@ +module Cms + module Providers + module Strapi + module Queries + class HeaderMenu + def self.embed(name) + <<~GRAPHQL.freeze + #{name} { + label + menuItems { + label + url + } + } + GRAPHQL + end + end + end + end + end +end diff --git a/app/services/cms/singles/header.rb b/app/services/cms/singles/header.rb new file mode 100644 index 0000000000..1d0d13009f --- /dev/null +++ b/app/services/cms/singles/header.rb @@ -0,0 +1,19 @@ +module Cms + module Singles + class Header < Resource + def self.resource_attribute_mappings + [ + {model: Models::HeaderMenu, key: :dropDowns} + ] + end + + def self.cache_expiry + 15.minutes + end + + def self.resource_key = "header" + + def self.graphql_key = "header" + end + end +end diff --git a/app/views/components/_header.html.erb b/app/views/components/_header.html.erb index a943a64516..3694445c09 100644 --- a/app/views/components/_header.html.erb +++ b/app/views/components/_header.html.erb @@ -53,21 +53,12 @@