diff --git a/examples/nuxt-app/layers/example-data-driven-component/README.md b/examples/nuxt-app/layers/example-data-driven-component/README.md new file mode 100644 index 000000000..f3ae29188 --- /dev/null +++ b/examples/nuxt-app/layers/example-data-driven-component/README.md @@ -0,0 +1,19 @@ +## About this layer + +An example of setting up a data driven component for testing purposes + +Data driven components can be registered in the Nuxt `app.config` file: + +```ts +export default defineAppConfig({ + ripple: { + dataDrivenComponents: { + // add key of field_data_driven_component and value of component name to render + // eg: find_a_council_map: 'VicCouncilLookup' + example_ddc: 'ExampleDDC' + } + } +}) +``` + +Note that the vue component (e.g. ExampleDDC) must be declared globally for this to work, see https://nuxt.com/docs/guide/directory-structure/components. diff --git a/examples/nuxt-app/layers/example-data-driven-component/app.config.ts b/examples/nuxt-app/layers/example-data-driven-component/app.config.ts new file mode 100644 index 000000000..e43f2b936 --- /dev/null +++ b/examples/nuxt-app/layers/example-data-driven-component/app.config.ts @@ -0,0 +1,12 @@ +export default defineAppConfig({ + ripple: { + featureFlags: { + contentCollectionSearchConnector: 'elasticsearch' + }, + dataDrivenComponents: { + // add key of field_data_driven_component and value of component name to render + // eg: find_a_council_map: 'VicCouncilLookup' + example_ddc: 'ExampleDDC' + } + } +}) diff --git a/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue b/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue new file mode 100644 index 000000000..350e0163d --- /dev/null +++ b/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue @@ -0,0 +1,15 @@ + + + diff --git a/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts b/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts new file mode 100644 index 000000000..f716d9f9d --- /dev/null +++ b/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts @@ -0,0 +1,3 @@ +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({}) diff --git a/examples/nuxt-app/nuxt.config.ts b/examples/nuxt-app/nuxt.config.ts index b306f8f43..fd1c319c2 100644 --- a/examples/nuxt-app/nuxt.config.ts +++ b/examples/nuxt-app/nuxt.config.ts @@ -25,7 +25,8 @@ export default defineNuxtConfig({ './layers/example-components', './layers/fixture-api', './layers/map-features', - './layers/ripple-ui-forms-ext' + './layers/ripple-ui-forms-ext', + './layers/example-data-driven-component' ], // Nuxt devtools sourcemap: true, diff --git a/examples/nuxt-app/test/features/landingpage/page-components.feature b/examples/nuxt-app/test/features/landingpage/page-components.feature index 390611cee..e3c378f2a 100644 --- a/examples/nuxt-app/test/features/landingpage/page-components.feature +++ b/examples/nuxt-app/test/features/landingpage/page-components.feature @@ -209,3 +209,9 @@ Feature: Home page Then the dataLayer should include the following events | event | element_id | element_text | label | file_name | file_extension | type | component | | file_download | page-component-1951 | Download it | Complex image | medium.png | png | image | rpl-media-embed | + + @mockserver + Scenario: Page component - Data driven component + Then a custom data driven component with ID "3553540" should exist with title "Test data driven title" and have the properties + | description | testCustomProp | + | Test data driven desc | testCustomValue | diff --git a/examples/nuxt-app/test/fixtures/landingpage/home.json b/examples/nuxt-app/test/fixtures/landingpage/home.json index 793f0e3d4..2ec2bcd7d 100644 --- a/examples/nuxt-app/test/fixtures/landingpage/home.json +++ b/examples/nuxt-app/test/fixtures/landingpage/home.json @@ -981,6 +981,18 @@ "allowFullscreen": true, "showTitle": true } + }, + { + "uuid": "0aa14648-604d-4aab-833a-bbce065433f4", + "component": "ExampleDDC", + "id": "3553540", + "props": { + "title": "Test data driven title", + "description": "

Test data driven desc

", + "field": "example_ddc", + "component": "ExampleDDC", + "testCustomProp": "testCustomValue" + } } ], "meta": { diff --git a/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts b/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts new file mode 100644 index 000000000..b859fe85c --- /dev/null +++ b/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts @@ -0,0 +1,22 @@ +import { Then, DataTable } from '@badeball/cypress-cucumber-preprocessor' + +Then( + 'a custom data driven component with ID {string} should exist with title {string} and have the properties', + (id: string, title: string, dataTable: DataTable) => { + const data = dataTable.hashes()[0] + + cy.get(`[data-component-id="${id}"]`).as('component') + cy.get('@component').should('exist') + cy.get(`@component`).should( + 'have.attr', + 'data-component-type', + 'ExampleDDC' + ) + cy.get('@component').within(() => { + cy.get(`h3`).should('have.text', title) + }) + + cy.get('@component').should('contain', data.description) + cy.get('@component').should('contain', data.testCustomProp) + } +) diff --git a/packages/ripple-test-utils/step_definitions/components/index.ts b/packages/ripple-test-utils/step_definitions/components/index.ts index db59dde95..555fff06e 100644 --- a/packages/ripple-test-utils/step_definitions/components/index.ts +++ b/packages/ripple-test-utils/step_definitions/components/index.ts @@ -20,3 +20,4 @@ import './category-grid' import './content-collection' import './custom-collection' import './social-share' +import './data-driven-component' diff --git a/packages/ripple-tide-landing-page/app.config.ts b/packages/ripple-tide-landing-page/app.config.ts index b1cbeba1c..eedaf4ea1 100644 --- a/packages/ripple-tide-landing-page/app.config.ts +++ b/packages/ripple-tide-landing-page/app.config.ts @@ -2,6 +2,10 @@ export default defineAppConfig({ ripple: { featureFlags: { contentCollectionSearchConnector: 'elasticsearch' + }, + dataDrivenComponents: { + // add key of field_data_driven_component and value of component name to render + // eg: find_a_council_map: 'VicCouncilLookup' } } }) diff --git a/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue b/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue new file mode 100644 index 000000000..1aa800412 --- /dev/null +++ b/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ripple-tide-landing-page/mapping/components.ts b/packages/ripple-tide-landing-page/mapping/components.ts index 0d50767ae..feb7dd00d 100644 --- a/packages/ripple-tide-landing-page/mapping/components.ts +++ b/packages/ripple-tide-landing-page/mapping/components.ts @@ -19,6 +19,7 @@ import complexImageMapping from './components/complex-image/complex-image-mappin import dataTableMapping from './components/data-table/data-table-mapping' import compactCardsMapping from './components/compact-cards/compact-cards-mapping' import openFormsMapping from './components/openforms/openforms-mapping' +import dataDrivenComponentMapping from './components/data-driven-component/data-driven-component-mapping' const mappings: { [key: string]: { @@ -45,7 +46,8 @@ const mappings: { 'paragraph--complex_image': complexImageMapping, 'paragraph--data_table': dataTableMapping, 'paragraph--compact_card_collection': compactCardsMapping, - 'paragraph--form_embed_openforms': openFormsMapping + 'paragraph--form_embed_openforms': openFormsMapping, + 'paragraph--data_driven_component': dataDrivenComponentMapping } // Add reusable include on whitelisted paragraph types diff --git a/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts b/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts new file mode 100644 index 000000000..b31cf498c --- /dev/null +++ b/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts @@ -0,0 +1,60 @@ +/* eslint-disable no-prototype-builtins */ +import type { TideDynamicPageComponent } from '@dpc-sdp/ripple-tide-api/types' +import { logger } from '@dpc-sdp/ripple-tide-api' +import { useAppConfig } from '#imports' + +export interface ITideDataDrivenComponent { + component?: string + description: string + title: string + field?: string + data?: unknown +} + +interface IDDCAppConfig { + ripple?: { + dataDrivenComponents?: Record + } +} + +export const dataDrivenComponentMapping = ( + field: any +): TideDynamicPageComponent => { + const getComponent = (field: any) => { + const appConfig = useAppConfig() as IDDCAppConfig + if ( + appConfig?.ripple?.dataDrivenComponents?.hasOwnProperty( + field.field_data_driven_component + ) + ) { + return appConfig.ripple.dataDrivenComponents[ + field.field_data_driven_component + ] + } + return 'TideLandingPageDataDrivenCmpError' + } + let dataProps = {} + try { + dataProps = JSON.parse(field.field_configuration) + } catch (error) { + logger.error(`Error parsing data driven component extra data`, error) + } + + return { + component: getComponent(field), + id: `${field.drupal_internal__id}`, + props: { + title: field.field_paragraph_title, + description: field.field_paragraph_body?.processed, + field: field.field_data_driven_component, + component: getComponent(field), + ...dataProps + } + } +} + +export default { + includes: [], + mapping: dataDrivenComponentMapping, + contentTypes: ['landing_page'] +}