diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
index 8a4f856e8..52c9a86c2 100644
--- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
+++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
@@ -175,11 +175,11 @@ Array [
"keys": Array [
Object {
"key": "curiosity-inventory.header",
- "match": "translate('curiosity-inventory.header', { context: key })",
+ "match": "translate('curiosity-inventory.header', { context: id })",
},
Object {
"key": "curiosity-inventory.header",
- "match": "translate('curiosity-inventory.header', { context: id })",
+ "match": "translate('curiosity-inventory.header', { context: key })",
},
],
},
diff --git a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap
index 8acbc42d4..0666d0567 100644
--- a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap
+++ b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHelpers.test.js.snap
@@ -1,5 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop invalid, null 1`] = `undefined`;
+
+exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, function 1`] = `"ipsum"`;
+
+exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, number 1`] = `1000`;
+
+exports[`InventoryListHelpers applyConfigProperty should determine if filter property is valid and return it or undefined: prop valid, string 1`] = `"hello world"`;
+
exports[`InventoryListHelpers applySortFilters should apply and return updated filters for table sorting: NOT sortable 1`] = `
Object {
"id": "lorem",
@@ -138,6 +146,35 @@ Object {
}
`;
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom and generated transforms 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title": "ipsum",
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "transforms": Array [
+ [Function],
+ [Function],
+ ],
+ },
+ ],
+ "data": Object {
+ "dolor": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})",
+ "value": "sit",
+ },
+ "lorem": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "value": "ipsum",
+ },
+ },
+}
+`;
+
exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom callback data 1`] = `
Object {
"cells": Array [
@@ -193,6 +230,82 @@ Object {
}
`;
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell tooltips 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title":
+ object, body, lorem
+ ,
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "title":
+ object, header, lorem
+ ,
+ "transforms": Array [],
+ },
+ ],
+ "data": Object {
+ "dolor": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})",
+ "value": "sit",
+ },
+ "lorem": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "value": "ipsum",
+ },
+ },
+}
+`;
+
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell, hide cell and column header if data is missing 1`] = `
+Object {
+ "cells": Array [],
+ "columnHeaders": Array [],
+ "data": Object {},
+}
+`;
+
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom cell, use an empty cell if data is missing 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title": "",
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"missing\\"})",
+ "transforms": Array [],
+ },
+ ],
+ "data": Object {
+ "hello": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"hello\\"})",
+ "value": "world",
+ },
+ },
+}
+`;
+
exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom header data 1`] = `
Object {
"cells": Array [
@@ -222,6 +335,63 @@ Object {
}
`;
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom sort 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title": "ipsum",
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "onSort": [Function],
+ "sortActive": undefined,
+ "sortDirection": undefined,
+ "title": "header, lorem sort",
+ "transforms": Array [],
+ },
+ ],
+ "data": Object {
+ "dolor": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})",
+ "value": "sit",
+ },
+ "lorem": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "value": "ipsum",
+ },
+ },
+}
+`;
+
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: custom transforms 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title": "ipsum",
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "transforms": Array [
+ [Function],
+ ],
+ },
+ ],
+ "data": Object {
+ "dolor": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})",
+ "value": "sit",
+ },
+ "lorem": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "value": "ipsum",
+ },
+ },
+}
+`;
+
exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: filtered data 1`] = `
Object {
"cells": Array [
@@ -248,8 +418,38 @@ Object {
}
`;
+exports[`InventoryListHelpers parseRowCellsListData should parse and return formatted/filtered table cells.: generated transforms 1`] = `
+Object {
+ "cells": Array [
+ Object {
+ "title": "ipsum",
+ },
+ ],
+ "columnHeaders": Array [
+ Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "transforms": Array [
+ [Function],
+ ],
+ },
+ ],
+ "data": Object {
+ "dolor": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"dolor\\"})",
+ "value": "sit",
+ },
+ "lorem": Object {
+ "title": "t(curiosity-inventory.header, {\\"context\\":\\"lorem\\"})",
+ "value": "ipsum",
+ },
+ },
+}
+`;
+
exports[`InventoryListHelpers should have specific functions: inventoryListHelpers 1`] = `
Object {
+ "applyConfigProperty": [Function],
+ "applyHeaderRowCellFilters": [Function],
"applySortFilters": [Function],
"applyWrappableFilters": [Function],
"parseInventoryFilters": [Function],
diff --git a/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js b/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js
index 27ba89c9e..d559d0778 100644
--- a/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js
+++ b/src/components/inventoryCard/__tests__/inventoryCardHelpers.test.js
@@ -1,5 +1,6 @@
import {
inventoryCardHelpers,
+ applyConfigProperty,
applySortFilters,
applyWrappableFilters,
parseInventoryFilters,
@@ -51,6 +52,70 @@ describe('InventoryListHelpers', () => {
expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom header data');
filters[0] = {
+ id: 'lorem',
+ header: {
+ title: 'header, lorem sort'
+ },
+ onSort: () => {}
+ };
+
+ expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom sort');
+
+ filters[0] = {
+ id: 'lorem',
+ transforms: [() => {}]
+ };
+
+ expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom transforms');
+
+ filters[0] = {
+ id: 'lorem',
+ transforms: [() => {}],
+ cellWidth: 200
+ };
+
+ expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom and generated transforms');
+
+ filters[0] = {
+ id: 'lorem',
+ cellWidth: 200
+ };
+
+ expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('generated transforms');
+
+ filters[0] = {
+ id: 'missing'
+ };
+
+ expect(parseRowCellsListData({ filters, cellData: { hello: 'world' } })).toMatchSnapshot(
+ 'custom cell, use an empty cell if data is missing'
+ );
+
+ filters[0] = {
+ id: 'missing',
+ showEmptyCell: false
+ };
+
+ expect(parseRowCellsListData({ filters, cellData: {} })).toMatchSnapshot(
+ 'custom cell, hide cell and column header if data is missing'
+ );
+
+ filters[0] = {
+ id: 'lorem',
+ header: {
+ title: 'object, header, lorem',
+ tooltip: 'tooltip header content'
+ },
+ cell: {
+ title: 'object, body, lorem',
+ tooltip: 'tooltip body content'
+ }
+ };
+
+ expect(parseRowCellsListData({ filters, cellData })).toMatchSnapshot('custom cell tooltips');
+
+ filters[0] = {
+ id: 'lorem',
header: ({ lorem, dolor }) => `${lorem.title}/${dolor.title}`,
cell: ({ lorem, dolor }) => `${lorem.value}/${dolor.value}`
};
@@ -72,6 +137,26 @@ describe('InventoryListHelpers', () => {
expect(parseInventoryFilters({ filters, onSort: () => {} })).toMatchSnapshot('sortable');
});
+ it('applyConfigProperty should determine if filter property is valid and return it or undefined', () => {
+ const filter = {
+ title: 'hello world'
+ };
+
+ expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, string');
+
+ filter.title = 1000;
+
+ expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, number');
+
+ filter.title = ({ lorem }) => lorem;
+
+ expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop valid, function');
+
+ filter.title = null;
+
+ expect(applyConfigProperty(filter.title, { params: { lorem: 'ipsum' } })).toMatchSnapshot('prop invalid, null');
+ });
+
it('applySortFilters should apply and return updated filters for table sorting', () => {
const filter = {
id: 'lorem',
diff --git a/src/components/inventoryCard/inventoryCardHelpers.js b/src/components/inventoryCard/inventoryCardHelpers.js
index 0ef0cf6b1..a8504489f 100644
--- a/src/components/inventoryCard/inventoryCardHelpers.js
+++ b/src/components/inventoryCard/inventoryCardHelpers.js
@@ -1,6 +1,8 @@
import React from 'react';
import { cellWidth as PfCellWidth, SortByDirection, wrappable } from '@patternfly/react-table';
import _camelCase from 'lodash/camelCase';
+import _isPlainObject from 'lodash/isPlainObject';
+import { Tooltip } from '../tooltip/tooltip';
import { translate } from '../i18n/i18n';
import {
RHSM_API_QUERY_SORT_DIRECTION_TYPES as SORT_DIRECTION_TYPES,
@@ -9,16 +11,150 @@ import {
import { helpers } from '../../common';
/**
- * Apply sort filter to filters.
+ * ToDo: review setting up a transformed cell cache for already transformed cells.
+ * - review using a simple state and key memoized component
+ * - review using lru cache in a inventoryCardContext custom hook
+ */
+
+/**
+ * Apply product inventory config properties consistently.
+ *
+ * @param {Function|string|number} prop
+ * @param {object} options
+ * @param {*[]|*} options.params
+ * @returns {React.ReactNode}
+ */
+const applyConfigProperty = (prop, { params = [] } = {}) => {
+ let updatedProp = prop;
+
+ if (typeof prop === 'function') {
+ updatedProp = prop(...((Array.isArray(params) && params) || [params]));
+ }
+
+ if (typeof updatedProp === 'string' || typeof updatedProp === 'number' || React.isValidElement(updatedProp)) {
+ return updatedProp;
+ }
+
+ return undefined;
+};
+
+/**
+ * Generate header and row cell configuration from filters.
+ *
+ * @param {Array<{id: string, cell:React.ReactNode|{ title: string }, cellWidth: number,
+ * header:React.ReactNode|{ title: string }, onSort: Function, showEmptyCell: boolean,
+ * sortId: string, sortActive: boolean, sortDirection: string,
+ * transforms: Array}>} filters
+ * @param {object} cellData
+ * @param {object} session
+ * @returns {{bodyCells: { title: React.ReactNode }[], headerCells: { title: React.ReactNode }[]}}
+ */
+const applyHeaderRowCellFilters = (filters = [], cellData = {}, session = {}) => {
+ const headerCells = [];
+ const bodyCells = [];
+
+ filters.forEach(
+ ({ id, cell, cellWidth, header, onSort, showEmptyCell = true, sortId, sortActive, sortDirection, transforms }) => {
+ const headerCellUpdated = { title: translate('curiosity-inventory.header', { context: id }), transforms: [] };
+ const bodyCellUpdated = { title: '' };
+
+ // set filtered base header and body cells, or if filter doesn't exist skip
+ if (cellData[id]) {
+ headerCellUpdated.title = cellData[id]?.title ?? id;
+ bodyCellUpdated.title = cellData[id]?.value ?? '';
+ } else {
+ if (helpers.DEV_MODE || helpers.REVIEW_MODE) {
+ console.warn(`Warning: Filter "${id}" not found in "table row" response data.`, cellData);
+ }
+ if (showEmptyCell === false) {
+ return;
+ }
+ }
+
+ // set header cell title
+ if (header) {
+ const updatedHeaderCellTitle = applyConfigProperty(header, { params: [{ ...cellData }, { ...session }] });
+ if (updatedHeaderCellTitle) {
+ headerCellUpdated.title = updatedHeaderCellTitle;
+ } else if (_isPlainObject(header)) {
+ Object.assign(headerCellUpdated, { ...header });
+ }
+
+ // set header cell tooltip
+ if (header.tooltip && headerCellUpdated.title) {
+ const updatedHeaderCellTooltip = applyConfigProperty(header.tooltip, {
+ params: [{ ...cellData }, { ...session }]
+ });
+ if (updatedHeaderCellTooltip) {
+ headerCellUpdated.title = {headerCellUpdated.title};
+ }
+
+ delete headerCellUpdated.tooltip;
+ }
+ }
+
+ // set header cell transforms
+ if (Array.isArray(headerCellUpdated.transforms)) {
+ if (Array.isArray(transforms)) {
+ headerCellUpdated.transforms = headerCellUpdated.transforms.concat([...transforms]);
+ }
+
+ if (typeof cellWidth === 'number') {
+ headerCellUpdated.transforms.push(PfCellWidth(cellWidth));
+ }
+ }
+
+ // set header cell onSort
+ if (typeof onSort === 'function') {
+ headerCellUpdated.onSort = obj => onSort({ ...cellData }, { ...obj, id: sortId || id });
+ headerCellUpdated.sortActive = sortActive;
+ headerCellUpdated.sortDirection = sortDirection;
+ }
+
+ // set body cell title
+ if (cell) {
+ const updatedBodyCellTitle = applyConfigProperty(cell, { params: [{ ...cellData }, { ...session }] });
+ if (updatedBodyCellTitle) {
+ bodyCellUpdated.title = updatedBodyCellTitle;
+ } else if (_isPlainObject(cell)) {
+ Object.assign(bodyCellUpdated, { ...cell });
+ }
+
+ // set body cell tooltip
+ if (cell.tooltip && bodyCellUpdated.title) {
+ const updatedBodyCellTooltip = applyConfigProperty(cell.tooltip, {
+ params: [{ ...cellData }, { ...session }]
+ });
+ if (updatedBodyCellTooltip) {
+ bodyCellUpdated.title = {bodyCellUpdated.title};
+ }
+
+ delete bodyCellUpdated.tooltip;
+ }
+ }
+
+ headerCells.push(headerCellUpdated);
+ bodyCells.push(bodyCellUpdated);
+ }
+ );
+
+ return {
+ headerCells,
+ bodyCells
+ };
+};
+
+/**
+ * Shallow clone filter, and apply a column sort filter.
*
* @param {object} params
- * @param {{ onSort: Function, sortActive: boolean, sortDirection: string, isSortDefault: boolean,
- * sortDefaultInitialDirection: string }} params.filter
+ * @param {{onSort: Function, sortActive: boolean, sortDirection: string, isSortDefault: boolean,
+ * sortDefaultInitialDirection: string}} params.filter
* @param {Function} params.onSort
* @param {object} params.query
- * @returns {object}
+ * @returns {{}}
*/
-const applySortFilters = ({ filter = {}, onSort, query = {} }) => {
+const applySortFilters = ({ filter = {}, onSort, query = {} } = {}) => {
const { id, sortId } = filter;
const updatedId = sortId || id;
const updatedFilter = { ...filter };
@@ -66,7 +202,14 @@ const applySortFilters = ({ filter = {}, onSort, query = {} }) => {
return updatedFilter;
};
-const applyWrappableFilters = ({ filter = {} }) => {
+/**
+ * Shallow clone and apply a consistent PF "wrappable" transformation config allowing column content to wrap.
+ *
+ * @param {object} params
+ * @param {object} params.filter
+ * @returns {{}}
+ */
+const applyWrappableFilters = ({ filter = {} } = {}) => {
const updatedFilter = { ...filter };
if (Array.isArray(updatedFilter.transforms)) {
@@ -79,15 +222,18 @@ const applyWrappableFilters = ({ filter = {} }) => {
};
/**
- * Apply additional properties to filters.
+ * Shallow clone and apply, sequence specific, additional properties to filters.
*
* @param {object} params
- * @param {Array} params.filters
+ * @param {Array<{id: string, cell:*, cellWidth: number, header:*, onSort: Function,
+ * showEmptyCell: boolean, sortId: string, sortActive: boolean,
+ * sortDirection: string, transforms: Array, isSortDefault: boolean,
+ * sortDefaultInitialDirection: string}>} params.filters
* @param {Function} params.onSort
* @param {object} params.query
- * @returns {Array}
+ * @returns {*[]}
*/
-const parseInventoryFilters = ({ filters = [], onSort, query = {} }) =>
+const parseInventoryFilters = ({ filters = [], onSort, query = {} } = {}) =>
[...filters].map(filter => {
const updatedFilter = { ...filter };
@@ -106,105 +252,39 @@ const parseInventoryFilters = ({ filters = [], onSort, query = {} }) =>
* Parse and return formatted/filtered table cells, and apply table filters.
*
* @param {object} params
- * @param {Array} params.filters
+ * @param {Array<{id: string, cell:React.ReactNode|{ title: string }, cellWidth: number,
+ * header:React.ReactNode|{ title: string }, onSort: Function, showEmptyCell: boolean,
+ * sortId: string, sortActive: boolean, sortDirection: string,
+ * transforms: Array}>} params.filters
* @param {object} params.cellData
* @param {object} params.session
- * @returns {{columnHeaders: Array, cells: Array, data: object}}
+ * @returns {{columnHeaders: { title: React.ReactNode }[], cells: { title: React.ReactNode }[], data: {}}}
*/
-const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} }) => {
+const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} } = {}) => {
const updatedColumnHeaders = [];
const updatedCells = [];
const allCells = {};
// Apply basic translation and value
- Object.entries(cellData).forEach(([key, value]) => {
+ Object.entries(cellData).forEach(([key, value = '']) => {
allCells[key] = {
title: translate('curiosity-inventory.header', { context: key }),
value
};
updatedColumnHeaders.push(allCells[key].title);
- updatedCells.push(value);
+ updatedCells.push(value || '...');
});
// Apply filters to header and cell values
- if (filters?.length) {
+ if (filters?.length && Object.keys(allCells).length) {
updatedColumnHeaders.length = 0;
updatedCells.length = 0;
- filters.forEach(({ id, cell, cellWidth, header, onSort, sortId, sortActive, sortDirection, transforms }) => {
- let headerUpdated;
- let cellUpdated;
-
- if (allCells[id]) {
- headerUpdated = allCells[id]?.title ?? id;
- cellUpdated = allCells[id]?.value ?? '';
- } else if (id) {
- headerUpdated = translate('curiosity-inventory.header', { context: id });
- cellUpdated = '';
- }
-
- // set table header cell filter params
- if (header) {
- headerUpdated = (typeof header === 'function' && header({ ...allCells })) || header;
- }
-
- if (
- typeof headerUpdated === 'string' ||
- typeof headerUpdated === 'number' ||
- React.isValidElement(headerUpdated)
- ) {
- headerUpdated = {
- title: headerUpdated
- };
- }
-
- if (headerUpdated) {
- headerUpdated.transforms = [];
-
- if (Array.isArray(transforms)) {
- headerUpdated.transforms = headerUpdated.transforms.concat([...transforms]);
- }
-
- if (typeof cellWidth === 'number') {
- headerUpdated.transforms.push(PfCellWidth(cellWidth));
- }
- }
-
- if (typeof onSort === 'function') {
- headerUpdated = {
- ...headerUpdated,
- onSort: obj => onSort({ ...allCells }, { ...obj, id: sortId || id }),
- sortActive,
- sortDirection
- };
- }
-
- // set table row cell filter params
- if (cell) {
- cellUpdated = typeof cell === 'function' ? cell({ ...allCells }, { ...session }) : cell;
- }
-
- if (typeof cellUpdated === 'string' || typeof cellUpdated === 'number' || React.isValidElement(cellUpdated)) {
- cellUpdated = {
- title: cellUpdated
- };
- } else if (!cellUpdated?.title) {
- if (helpers.DEV_MODE || helpers.REVIEW_MODE) {
- console.error(
- `PF table throws an error when cell values don't conform to what it is expecting, or align exactly to column headers.
- \n\nSee cell ID=${id} with VALUE=${cellUpdated}`
- );
- }
-
- cellUpdated = {
- title: ''
- };
- }
+ const { headerCells = [], bodyCells = [] } = applyHeaderRowCellFilters(filters, allCells, session);
- updatedColumnHeaders.push(headerUpdated);
- updatedCells.push(cellUpdated);
- });
+ updatedColumnHeaders.push(...headerCells);
+ updatedCells.push(...bodyCells);
}
return {
@@ -215,6 +295,8 @@ const parseRowCellsListData = ({ filters = [], cellData = {}, session = {} }) =>
};
const inventoryCardHelpers = {
+ applyConfigProperty,
+ applyHeaderRowCellFilters,
applySortFilters,
applyWrappableFilters,
parseInventoryFilters,
@@ -224,6 +306,8 @@ const inventoryCardHelpers = {
export {
inventoryCardHelpers as default,
inventoryCardHelpers,
+ applyConfigProperty,
+ applyHeaderRowCellFilters,
applySortFilters,
applyWrappableFilters,
parseInventoryFilters,
diff --git a/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap
new file mode 100644
index 000000000..53286cd2a
--- /dev/null
+++ b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Tooltip Component should allow custom properties: custom, content 1`] = `
+
+ tooltip content
+
+ }
+ distance={5}
+ enableFlip={false}
+ entryDelay={100}
+ exitDelay={0}
+ position="top"
+>
+
+ content
+
+
+`;
+
+exports[`Tooltip Component should render a basic component: basic 1`] = `
+
+ ...
+
+ }
+ distance={5}
+ enableFlip={false}
+ entryDelay={100}
+ exitDelay={0}
+ position="top"
+>
+
+ hello world
+
+
+`;
diff --git a/src/components/tooltip/__tests__/tooltip.test.js b/src/components/tooltip/__tests__/tooltip.test.js
new file mode 100644
index 000000000..98b1ad5db
--- /dev/null
+++ b/src/components/tooltip/__tests__/tooltip.test.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { Tooltip } from '../tooltip';
+
+describe('Tooltip Component', () => {
+ it('should render a basic component', () => {
+ const props = {};
+
+ const component = shallow(hello world);
+ expect(component).toMatchSnapshot('basic');
+ });
+
+ it('should allow custom properties', () => {
+ const props = {
+ content: tooltip content,
+ isNoWrap: true
+ };
+
+ const component = shallow(
+
+ content
+
+ );
+ expect(component).toMatchSnapshot('custom, content');
+ });
+});
diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js
new file mode 100755
index 000000000..caa18faba
--- /dev/null
+++ b/src/components/tooltip/tooltip.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Tooltip as PfTooltip, TooltipProps, TooltipPosition } from '@patternfly/react-core';
+
+/**
+ * PF tooltip wrapper component.
+ *
+ * @param {object} props
+ * @param {React.ReactNode} props.children
+ * @param {React.ReactNode} props.content
+ * @param {boolean} props.isNoWrap
+ * @param {TooltipProps} props.props
+ * @returns {React.ReactNode}
+ */
+const Tooltip = ({ children, content, isNoWrap, ...props }) => (
+ {content || ''}}
+ {...props}
+ >
+ {(React.isValidElement(children) && children) || {children}}
+
+);
+
+/**
+ * Prop types.
+ *
+ * @type {{children: React.ReactNode, content: React.ReactNode}}
+ */
+Tooltip.propTypes = {
+ children: PropTypes.node.isRequired,
+ content: PropTypes.node,
+ distance: PropTypes.number,
+ enableFlip: PropTypes.bool,
+ entryDelay: PropTypes.number,
+ exitDelay: PropTypes.number,
+ isNoWrap: PropTypes.bool,
+ position: PropTypes.string
+};
+
+/**
+ * Default props.
+ *
+ * @type {{content: string}}
+ */
+Tooltip.defaultProps = {
+ content: '...',
+ distance: 5,
+ enableFlip: false,
+ entryDelay: 100,
+ exitDelay: 0,
+ isNoWrap: false,
+ position: TooltipPosition.top
+};
+
+export { Tooltip as default, Tooltip };
diff --git a/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap b/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap
index 2df0e7d9d..031c87296 100644
--- a/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap
+++ b/src/config/__tests__/__snapshots__/product.rhosak.test.js.snap
@@ -233,7 +233,7 @@ Object {
"title": "",
},
Object {
- "title": "",
+ "title": "2022-01-01T00:00:00.000Z",
},
],
"columnHeaders": Array [
diff --git a/src/styles/_tooltip.scss b/src/styles/_tooltip.scss
new file mode 100644
index 000000000..f159b97ba
--- /dev/null
+++ b/src/styles/_tooltip.scss
@@ -0,0 +1,8 @@
+html > .curiosity-tooltip,
+.curiosity-tooltip {
+ &__nowrap {
+ white-space: nowrap;
+ }
+
+ &-children {}
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 0d50c2eef..0b6347105 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -5,6 +5,7 @@
// App
@import 'page-layout';
@import 'fade';
+@import 'tooltip';
@import 'product-view';
@import 'optin';
@import 'usage-graph';
diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap
index baa76ace8..4e43703f3 100644
--- a/tests/__snapshots__/code.test.js.snap
+++ b/tests/__snapshots__/code.test.js.snap
@@ -3,7 +3,7 @@
exports[`General code checks should only have specific console.[warn|log|info|error] methods: console methods 1`] = `
Array [
"components/inventoryCard/inventoryCardContext.js:127: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);",
- "components/inventoryCard/inventoryCardHelpers.js:194: console.error(",
+ "components/inventoryCard/inventoryCardHelpers.js:67: console.warn(\`Warning: Filter \\"\${id}\\" not found in \\"table row\\" response data.\`, cellData);",
"components/inventoryCard/inventoryList.deprecated.js:62: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);",
"components/inventoryCardSubscriptions/inventoryCardSubscriptionsContext.js:127: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);",
"redux/common/reduxHelpers.js:282: console.error(\`Error: Property \${prop} does not exist within the passed state.\`, state);",
diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap
index 71f0dcc12..69fe83a4a 100644
--- a/tests/__snapshots__/dist.test.js.snap
+++ b/tests/__snapshots__/dist.test.js.snap
@@ -44,6 +44,7 @@ Array [
"./dist/js/384*js",
"./dist/js/3935*js",
"./dist/js/3935*txt",
+ "./dist/js/3969*js",
"./dist/js/4004*js",
"./dist/js/4024*js",
"./dist/js/4044*js",
@@ -154,6 +155,7 @@ Array [
"./dist/sourcemaps/361*map",
"./dist/sourcemaps/384*map",
"./dist/sourcemaps/3935*map",
+ "./dist/sourcemaps/3969*map",
"./dist/sourcemaps/4004*map",
"./dist/sourcemaps/4024*map",
"./dist/sourcemaps/4044*map",