diff --git a/examples/nuxt-app/components/global/TableExtraContentComponentExample.vue b/examples/nuxt-app/components/global/TableExtraContentComponentExample.vue new file mode 100644 index 0000000000..3971d76b35 --- /dev/null +++ b/examples/nuxt-app/components/global/TableExtraContentComponentExample.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/examples/nuxt-app/test/features/search-listing/table.feature b/examples/nuxt-app/test/features/search-listing/table.feature index a16474199a..86a33a70cb 100644 --- a/examples/nuxt-app/test/features/search-listing/table.feature +++ b/examples/nuxt-app/test/features/search-listing/table.feature @@ -15,3 +15,31 @@ Feature: Search listing table layout And the search network request should be called with the "/search-listing/table/request" fixture And the search listing layout should be "table" + Given a data table with type "search-listing-layout-table" + Then the table should not display extra content + + @mockserver + Example: Table shows extra content using a custom component + Given the page endpoint for path "/search-listing-table-extra-components" returns fixture "/search-listing/table/page-extra-component" with status 200 + And the search network request is stubbed with fixture "/search-listing/table/response" and status 200 + When I visit the page "/search-listing-table-extra-components" + And the search network request should be called with the "/search-listing/table/request" fixture + And the search listing layout should be "table" + + Given a data table with type "search-listing-layout-table" + And the table should have the caption "My Table" + And the table should have the footer "Some notes about the table" + When I toggle the tables extra content row + Then the tables extra content should contain the text "1 Implemented" + + @mockserver + Example: Table shows extra structured content using object keys + Given the page endpoint for path "/search-listing-table-structured" returns fixture "/search-listing/table/page-extra-structured" with status 200 + And the search network request is stubbed with fixture "/search-listing/table/response" and status 200 + When I visit the page "/search-listing-table-structured" + And the search network request should be called with the "/search-listing/table/request" fixture + And the search listing layout should be "table" + + Given a data table with type "search-listing-layout-table" + When I toggle the tables extra content row + Then the tables extra content should contain the text "Review and begin implementing the Common Risk Assessment Framework" diff --git a/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-component.json b/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-component.json new file mode 100644 index 0000000000..08c7893029 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-component.json @@ -0,0 +1,133 @@ +{ + "title": "Table with component extra data", + "changed": "2022-11-02T12:47:29+11:00", + "created": "2022-11-02T12:47:29+11:00", + "type": "tide_search_listing", + "nid": "11dede11-10c0-111e1-1100-000000000330", + "showTopicTags": true, + "summary": "On 28 January 2023 the Victorian Government announced the implementation of the final recommendations of the Royal Commission into Family Violence. This marks our public commitment to implementing all 227 recommendations.", + "config": { + "searchListingConfig": { + "resultsPerPage": 40, + "customSort": [ + { "_score": "desc" }, + { "field_fv_recommendation_number": "asc" }, + { "_doc": "desc" } + ] + }, + "queryConfig": { + "multi_match": { + "query": "{{query}}", + "fields": [ + "title^3", + "field_landing_page_summary^2", + "body", + "field_paragraph_body", + "summary_processed" + ] + } + }, + "results": { + "layout": { + "component": "TideSearchResultsTable", + "props": { + "offset": 0, + "showExtraContent": true, + "caption": "My Table", + "footer": "Some notes about the table", + "headingType": { "horizontal": true, "vertical": true }, + "columns": [ + { + "label": "Recommendation", + "objectKey": "field_fv_recommendation_number" + }, + { "label": "Title", "component": "TideSearchListingTableLink" }, + { + "label": "Category", + "objectKey": "field_fv_recommendation_category_name" + }, + { "label": "Status", "objectKey": "fv_recommendation_status" } + ], + "extraContent": { + "component": "TableExtraContentComponentExample" + } + } + } + }, + "globalFilters": [ + { "terms": { "type": ["fv_recommendation"] } }, + { "terms": { "field_node_site": ["4"] } } + ], + "userFilters": [ + { + "id": "category", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "field_fv_recommendation_category_name.keyword" + }, + "aggregations": { + "field": "field_fv_recommendation_category_name.keyword", + "source": "elastic" + }, + "props": { + "id": "category", + "label": "Category", + "placeholder": "Select a category", + "type": "RplFormDropdown", + "multiple": true + } + }, + { + "id": "department", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "field_fv_recommendation_dpt_name.keyword" + }, + "aggregations": { + "field": "field_fv_recommendation_dpt_name.keyword", + "source": "elastic" + }, + "props": { + "id": "department", + "label": "Deparment", + "placeholder": "Select a department", + "type": "RplFormDropdown", + "multiple": true + } + }, + { + "id": "status", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "fv_recommendation_status.keyword" + }, + "aggregations": { + "field": "recommendation_status", + "source": "taxonomy" + }, + "props": { + "id": "status", + "label": "Status", + "placeholder": "Select a status", + "type": "RplFormDropdown", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Implemented", + "value": "Implemented" + }, + { + "id": "2", + "label": "Not Implemented", + "value": "Not Implemented" + } + ] + } + } + ] + } +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-structured.json b/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-structured.json new file mode 100644 index 0000000000..a7b115bfc6 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/table/page-extra-structured.json @@ -0,0 +1,137 @@ +{ + "title": "Table with structure extra data", + "changed": "2022-11-02T12:47:29+11:00", + "created": "2022-11-02T12:47:29+11:00", + "type": "tide_search_listing", + "nid": "11dede11-10c0-111e1-1100-000000000330", + "showTopicTags": true, + "summary": "On 28 January 2023 the Victorian Government announced the implementation of the final recommendations of the Royal Commission into Family Violence. This marks our public commitment to implementing all 227 recommendations.", + "config": { + "searchListingConfig": { + "resultsPerPage": 40, + "customSort": [ + { "_score": "desc" }, + { "field_fv_recommendation_number": "asc" }, + { "_doc": "desc" } + ] + }, + "queryConfig": { + "multi_match": { + "query": "{{query}}", + "fields": [ + "title^3", + "field_landing_page_summary^2", + "body", + "field_paragraph_body", + "summary_processed" + ] + } + }, + "results": { + "layout": { + "component": "TideSearchResultsTable", + "props": { + "showExtraContent": true, + "caption": "My Table", + "footer": "Some notes about the table", + "headingType": { "horizontal": true, "vertical": true }, + "columns": [ + { + "label": "Recommendation", + "objectKey": "field_fv_recommendation_number" + }, + { "label": "Title", "component": "TideSearchListingTableLink" }, + { + "label": "Category", + "objectKey": "field_fv_recommendation_category_name" + }, + { "label": "Status", "objectKey": "fv_recommendation_status" } + ], + "extraContent": { + "items": [ + { + "label": "More info", + "objectKey": "title" + } + ] + } + } + } + }, + "globalFilters": [ + { "terms": { "type": ["fv_recommendation"] } }, + { "terms": { "field_node_site": ["4"] } } + ], + "userFilters": [ + { + "id": "category", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "field_fv_recommendation_category_name.keyword" + }, + "aggregations": { + "field": "field_fv_recommendation_category_name.keyword", + "source": "elastic" + }, + "props": { + "id": "category", + "label": "Category", + "placeholder": "Select a category", + "type": "RplFormDropdown", + "multiple": true + } + }, + { + "id": "department", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "field_fv_recommendation_dpt_name.keyword" + }, + "aggregations": { + "field": "field_fv_recommendation_dpt_name.keyword", + "source": "elastic" + }, + "props": { + "id": "department", + "label": "Deparment", + "placeholder": "Select a department", + "type": "RplFormDropdown", + "multiple": true + } + }, + { + "id": "status", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "value": "fv_recommendation_status.keyword" + }, + "aggregations": { + "field": "recommendation_status", + "source": "taxonomy" + }, + "props": { + "id": "status", + "label": "Status", + "placeholder": "Select a status", + "type": "RplFormDropdown", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Implemented", + "value": "Implemented" + }, + { + "id": "2", + "label": "Not Implemented", + "value": "Not Implemented" + } + ] + } + } + ] + } +} diff --git a/packages/ripple-test-utils/step_definitions/components/data-table.ts b/packages/ripple-test-utils/step_definitions/components/data-table.ts index b16980216a..be9b389adc 100644 --- a/packages/ripple-test-utils/step_definitions/components/data-table.ts +++ b/packages/ripple-test-utils/step_definitions/components/data-table.ts @@ -1,4 +1,9 @@ -import { Then, Given, DataTable } from '@badeball/cypress-cucumber-preprocessor' +import { + Then, + Given, + When, + DataTable +} from '@badeball/cypress-cucumber-preprocessor' Given('a data table with ID {string}', (id: string) => { cy.get(`[data-component-id="${id}"]`).as('component') @@ -7,6 +12,11 @@ Given('a data table with ID {string}', (id: string) => { .should('have.attr', 'data-component-type', 'TideLandingPageDataTable') }) +Given('a data table with type {string}', (id: string) => { + cy.get(`[data-component-type="${id}"]`).as('component') + cy.get('@component').should('exist') +}) + Then( 'have {int} rows and {int} columns', (rowCount: number, colCount: number, dataTable: DataTable) => { @@ -44,3 +54,36 @@ Then('it should have no heading', () => { cy.get('table thead tr').should('not.exist') }) }) + +Then('the table should have the caption {string}', (text: string) => { + cy.get('@component').within(() => { + cy.get('caption').contains(text) + }) +}) + +Then('the table should have the footer {string}', (text: string) => { + cy.get('@component').within(() => { + cy.get('tfoot').contains(text) + }) +}) + +When('I toggle the tables extra content row', () => { + cy.get('@component').within(() => { + cy.get('.rpl-data-table__toggle').first().click() + }) +}) + +Then('the table should not display extra content', () => { + cy.get('@component').within(() => { + cy.get('.rpl-data-table__toggle').should('not.exist') + }) +}) + +Then( + 'the tables extra content should contain the text {string}', + (text: string) => { + cy.get('@component').within(() => { + cy.get('.rpl-data-table__details-content:visible').contains(text) + }) + } +) diff --git a/packages/ripple-tide-search/components/global/TideSearchResultsTable.vue b/packages/ripple-tide-search/components/global/TideSearchResultsTable.vue index 1103b74f0d..a14f560008 100644 --- a/packages/ripple-tide-search/components/global/TideSearchResultsTable.vue +++ b/packages/ripple-tide-search/components/global/TideSearchResultsTable.vue @@ -1,10 +1,14 @@ @@ -20,17 +24,64 @@ type tableColumnConfig = { props?: any } +type tableHeadingTypeConfig = { + horizontal: boolean + vertical: boolean +} + +type tableExtraContentItems = { + label: string + objectKey: string + component?: string +} + +type tableExtraContentConfig = { + component?: string + items?: tableExtraContentItems[] +} + interface Props { results: Record[] + offset?: number columns: tableColumnConfig[] + headingType?: tableHeadingTypeConfig + extraContent?: tableExtraContentConfig + showExtraContent?: boolean + caption?: string + footer?: string } -const props = defineProps() +const props = withDefaults(defineProps(), { + caption: '', + footer: '', + offset: 1, + extraContent: undefined, + showExtraContent: false, + headingType: () => ({ + horizontal: true, + vertical: false + }) +}) + +const getExtraContent = () => { + if (!props.extraContent) return null + + let content = { ...props.extraContent } + if (props.extraContent?.items) { + content.items = props.extraContent.items.map((item) => ({ + component: 'TideSearchListingTableValue', + heading: item?.label, + ...item + })) + } + return content +} const items = computed(() => { return (props.results || []).map((result) => { return { id: result._id, + __extraContent: getExtraContent(), ...(result._source as Record) } }) diff --git a/packages/ripple-ui-core/src/components/data-table/RplDataTable.stories.mdx b/packages/ripple-ui-core/src/components/data-table/RplDataTable.stories.mdx index ad6b44f780..6afef96d4e 100644 --- a/packages/ripple-ui-core/src/components/data-table/RplDataTable.stories.mdx +++ b/packages/ripple-ui-core/src/components/data-table/RplDataTable.stories.mdx @@ -5,7 +5,7 @@ import { ArgsTable } from '@storybook/addon-docs' import RplDataTable from './RplDataTable.vue' -import { RplDataTableColumns, RplDataTableItems, RplDataTableColumnConfig, RplDataTableComplexItems, RplDataTableObjectKeyColumnConfig, RplDataTableObjects, RplDataTableStructuredColumns, RplDataTableStructuredItems } from './fixtures/sample' +import { RplDataTableColumns, RplDataTableItems, RplDataTableColumnConfig, RplDataTableComplexItems, RplDataTableObjectKeyColumnConfig, RplDataTableObjects, RplDataTableStructuredColumns, RplDataTableStructuredItems, RplDataTableExtraComponents } from './fixtures/sample' import { a11yStoryCheck } from './../../../stories/interactions.js' export const SingleTemplate = (args) => ({ @@ -38,8 +38,7 @@ export const SingleTemplate = (args) => ({ headingType: { horizontal: true, vertical: true - }, - alignHidden: 1 + } }} > {SingleTemplate.bind()} @@ -58,8 +57,7 @@ export const SingleTemplate = (args) => ({ headingType: { horizontal: true, vertical: true - }, - alignHidden: 1 + } }} > {SingleTemplate.bind()} @@ -78,8 +76,7 @@ export const SingleTemplate = (args) => ({ headingType: { horizontal: true, vertical: false - }, - alignHidden: 1 + } }} > {SingleTemplate.bind()} @@ -99,7 +96,28 @@ export const SingleTemplate = (args) => ({ horizontal: true, vertical: true }, - alignHidden: 1 + offset: 1 + }} + > + {SingleTemplate.bind()} + + + + + {SingleTemplate.bind()} diff --git a/packages/ripple-ui-core/src/components/data-table/RplDataTableRow.vue b/packages/ripple-ui-core/src/components/data-table/RplDataTableRow.vue index c41044495c..7ac78dda18 100644 --- a/packages/ripple-ui-core/src/components/data-table/RplDataTableRow.vue +++ b/packages/ripple-ui-core/src/components/data-table/RplDataTableRow.vue @@ -21,20 +21,23 @@ export type tableRow = { [key: string]: any } +export type extraRowContentItem = { + heading: string + content?: string + objectKey?: string + component?: string +} + export type extraRowContent = { + component?: string html?: string - items?: { - label: string - content: string - }[] + items?: extraRowContentItem[] } interface Props { - content: any columns: tableColumnConfig[] - items: Array row: tableRow - extraContent?: extraRowContent | null + extraContent?: extraRowContent verticalHeader?: boolean offset: number caption?: string @@ -42,11 +45,8 @@ interface Props { } const props = withDefaults(defineProps(), { - content: '', - items: () => [], verticalHeader: true, caption: undefined, - offset: 1, extraContent: null }) @@ -90,13 +90,14 @@ const handleClick = () => { const hasComponent = (column: any) => typeof column === 'object' && column.hasOwnProperty('component') -const getCellText = (colIndex: number) => { - const column = props.columns[colIndex] - const objectKey = column.objectKey +const getCellText = (col?: number | string, value = '') => { + if (typeof col === 'undefined') return value + + const objectKey = typeof col === 'string' ? col : props.columns[col].objectKey return typeof props.row === 'object' && props.row.hasOwnProperty(objectKey) ? props.row[objectKey] - : '' + : value } @@ -123,7 +124,6 @@ const getCellText = (colIndex: number) => { - { + - + /> diff --git a/packages/ripple-ui-core/src/components/data-table/fixtures/sample.ts b/packages/ripple-ui-core/src/components/data-table/fixtures/sample.ts index 359236c44b..dcb6978b1a 100644 --- a/packages/ripple-ui-core/src/components/data-table/fixtures/sample.ts +++ b/packages/ripple-ui-core/src/components/data-table/fixtures/sample.ts @@ -21,7 +21,7 @@ export const RplDataTableItems = [ col3: 'R2 - C3', col4: 'R2 - C4', __extraContent: { - html: '

R2 test heading

R2 test content

' + html: '

R2 test heading

R2 test content

' } }, { @@ -88,11 +88,12 @@ export const RplDataTableStructuredItems = [ col2: 'R3 - C2', col3: 'R3 - C3', col4: 'R3 - C4', + col5: 'R3 - Extra content cell', __extraContent: { items: [ { - heading: 'R3 test heading', - content: 'R3 test content' + heading: 'R3 with object key', + objectKey: 'col5' } ] } @@ -102,25 +103,25 @@ export const RplDataTableStructuredItems = [ export const RplDataTableComplexItems = [ { title: 'Turtle', - url: 'www.google.com', + url: 'https://www.google.com', speed: 'Slow', type: 'Reptile' }, { title: 'Dog', - url: 'www.google.com', + url: 'https://duckduckgo.com', speed: 'Medium', type: 'Mammal' }, { title: 'Horse', - url: 'www.google.com', + url: 'https://www.vic.gov.au', speed: 'Fast', type: 'Mammal' }, { title: 'Cheetah', - url: 'www.google.com', + url: 'https://www.ripple.sdp.vic.gov.au', speed: 'Fastest', type: 'Mammal' } @@ -186,3 +187,50 @@ export const RplDataTableObjects = [ type: 'Bird' } ] + +export const RplDataTableExtraComponents = [ + { + name: 'George', + age: 20, + type: 'Lizard', + image: '/img/image-landscape-s.jpg', + __extraContent: { + component: { + props: { + item: Object + }, + template: `` + } + } + }, + { + name: 'Fred', + age: 50, + type: 'Cat', + __extraContent: { + component: { + props: { + item: Object + }, + template: ` +

{{ item.name }} the {{ item.age }} year old {{ item.type }} was an inspiration to us all.

` + } + } + }, + { + name: 'Sue', + age: 20, + type: 'Dog', + __extraContent: { + component: { + props: { + item: Object + }, + template: ` + + Find out more about {{ item.name }} the {{ item.age }} year old {{ item.type }} + ` + } + } + } +]