diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index a38c55cd9..fb806055f 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -7,7 +7,7 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { Provider, StyleSettingsMap, TimeQuery, TimeSeriesData, TimeSeriesDataRequest, TimeSeriesDataRequestSettings, TreeQuery, Viewport } from "@iot-app-kit/core"; import { AlarmsConfig, Annotations, Axis, LabelsConfig, LayoutConfig, LegendConfig, MessageOverrides, MinimalSizeConfig, MovementConfig, ScaleConfig, TableColumn, Trend } from "@synchro-charts/core"; -import { Item, TableItem, TableProps } from "@iot-app-kit/table"; +import { Item, RecursivePartial, TableItem, TableMessages, TableProps } from "@iot-app-kit/table"; import { BranchReference, SiteWiseAssetTreeNode } from "@iot-app-kit/source-iotsitewise"; import { ColumnDefinition, FilterTexts } from "./components/iot-resource-explorer/types"; import { TableProps as TableProps1 } from "@awsui/components-react/table"; @@ -64,6 +64,7 @@ export namespace Components { interface IotReactTable { "columnDefinitions": TableProps['columnDefinitions']; "items": TableItem[]; + "messageOverrides": TableMessages; "propertyFiltering": TableProps['propertyFiltering']; "sorting": TableProps['sorting']; } @@ -134,7 +135,7 @@ export namespace Components { "annotations": Annotations; "columnDefinitions": TableProps['columnDefinitions']; "items": Item[]; - "messageOverrides"?: MessageOverrides; + "messageOverrides"?: RecursivePartial; "propertyFiltering": TableProps['propertyFiltering']; "queries": TimeQuery[]; "settings": TimeSeriesDataRequestSettings; @@ -347,6 +348,7 @@ declare namespace LocalJSX { interface IotReactTable { "columnDefinitions": TableProps['columnDefinitions']; "items": TableItem[]; + "messageOverrides"?: TableMessages; "propertyFiltering"?: TableProps['propertyFiltering']; "sorting"?: TableProps['sorting']; } @@ -417,7 +419,7 @@ declare namespace LocalJSX { "annotations"?: Annotations; "columnDefinitions": TableProps['columnDefinitions']; "items": Item[]; - "messageOverrides"?: MessageOverrides; + "messageOverrides"?: RecursivePartial; "propertyFiltering"?: TableProps['propertyFiltering']; "queries": TimeQuery[]; "settings"?: TimeSeriesDataRequestSettings; diff --git a/packages/components/src/components/iot-table/iot-react-table.tsx b/packages/components/src/components/iot-table/iot-react-table.tsx index dda87de23..9f438dbc9 100644 --- a/packages/components/src/components/iot-table/iot-react-table.tsx +++ b/packages/components/src/components/iot-table/iot-react-table.tsx @@ -1,6 +1,6 @@ import { Component, Element, Host, Prop, h } from '@stencil/core'; import React, { FunctionComponent } from 'react'; -import { Table, TableItem, TableProps } from '@iot-app-kit/table'; +import { Table, TableItem, TableProps, TableMessages } from '@iot-app-kit/table'; import ReactDOM from 'react-dom'; @Component({ @@ -15,6 +15,8 @@ export class IotReactTable { @Prop() propertyFiltering: TableProps['propertyFiltering']; + @Prop() messageOverrides: TableMessages; + @Element() host: HTMLElement; componentWillLoad() {} @@ -29,6 +31,7 @@ export class IotReactTable { columnDefinitions: this.columnDefinitions, sorting: this.sorting, propertyFiltering: this.propertyFiltering, + messageOverrides: this.messageOverrides, }; ReactDOM.render(React.createElement(Table as FunctionComponent, props), this.host); } diff --git a/packages/components/src/components/iot-table/iot-table.tsx b/packages/components/src/components/iot-table/iot-table.tsx index 732df9f3c..fd13b0919 100644 --- a/packages/components/src/components/iot-table/iot-table.tsx +++ b/packages/components/src/components/iot-table/iot-table.tsx @@ -1,5 +1,5 @@ import { Component, Prop, h, State, Listen, Watch } from '@stencil/core'; -import { Annotations, MessageOverrides, TableColumn, Trend, getThresholds } from '@synchro-charts/core'; +import { Annotations, TableColumn, Trend, getThresholds } from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -11,7 +11,15 @@ import { ProviderWithViewport, } from '@iot-app-kit/core'; import { v4 as uuidv4 } from 'uuid'; -import { createTableItems, Item, TableProps } from '@iot-app-kit/table'; +import { + createTableItems, + Item, + TableProps, + DefaultTableMessages, + TableMessages, + RecursivePartial, +} from '@iot-app-kit/table'; +import merge from 'lodash/merge'; @Component({ tag: 'iot-table', @@ -20,7 +28,7 @@ import { createTableItems, Item, TableProps } from '@iot-app-kit/table'; export class IotTable { @Prop() annotations: Annotations; - @Prop() messageOverrides?: MessageOverrides; + @Prop() messageOverrides?: RecursivePartial; @Prop() trends: Trend[]; @@ -46,6 +54,8 @@ export class IotTable { @State() provider: ProviderWithViewport; + private messages: TableMessages; + private defaultSettings: TimeSeriesDataRequestSettings = { resolution: '0', fetchMostRecentBeforeEnd: true, @@ -66,6 +76,7 @@ export class IotTable { } componentWillLoad() { + this.messages = merge(DefaultTableMessages, this.messageOverrides); this.buildProvider(); } @@ -91,14 +102,18 @@ export class IotTable { return ( ); }} diff --git a/packages/table/src/table/index.tsx b/packages/table/src/table/index.tsx index 8ee548be5..1112299a9 100644 --- a/packages/table/src/table/index.tsx +++ b/packages/table/src/table/index.tsx @@ -2,10 +2,16 @@ import React, { FunctionComponent } from 'react'; import { PropertyFilter, Table as AWSUITable } from '@awsui/components-react'; import { useCollection } from '@awsui/collection-hooks'; import { TableProps } from '../utils'; -import { defaultI18nStrings, getDefaultColumnDefinitions } from '../utils/tableHelpers'; +import { getDefaultColumnDefinitions } from '../utils/tableHelpers'; export const Table: FunctionComponent = (props) => { - const { items: userItems, sorting = {}, propertyFiltering, columnDefinitions: userColumnDefinitions } = props; + const { + items: userItems, + sorting = {}, + propertyFiltering, + columnDefinitions: userColumnDefinitions, + messageOverrides: { propertyFilter }, + } = props; const { items, collectionProps, propertyFilterProps } = useCollection(userItems, { sorting, propertyFiltering }); const columnDefinitions = getDefaultColumnDefinitions(userColumnDefinitions); return ( @@ -14,7 +20,7 @@ export const Table: FunctionComponent = (props) => { items={items} {...collectionProps} columnDefinitions={columnDefinitions} - filter={propertyFiltering && } + filter={propertyFiltering && } /> ); }; diff --git a/packages/table/src/utils/createCellItem.spec.ts b/packages/table/src/utils/createCellItem.spec.ts index e31c16d28..ec562df6f 100644 --- a/packages/table/src/utils/createCellItem.spec.ts +++ b/packages/table/src/utils/createCellItem.spec.ts @@ -1,25 +1,33 @@ import { createCellItem } from './createCellItem'; import { CellItem } from './types'; +import { DefaultTableMessages, TableMessages } from './messages'; -describe('createCellItem', () => { - it('creates CellItem', () => { - const props = { value: 10, error: undefined, isLoading: false, threshold: undefined }; - const item: CellItem = createCellItem(props); - expect(item).toMatchObject({ value: 10 }); - expect(item.valueOf()).toEqual(10); - }); +const messageOverride = { tableCell: { loading: 'Override loading text' } } as TableMessages; - it('creates CellItem that returns error message on error', () => { - const props = { value: 10, error: { msg: 'Some error' }, isLoading: false, threshold: undefined }; - const item: CellItem = createCellItem(props); +it('creates CellItem', () => { + const props = { value: 10, error: undefined, isLoading: false, threshold: undefined }; + const item: CellItem = createCellItem(props, DefaultTableMessages); + expect(item).toMatchObject({ value: 10 }); + expect(item.valueOf()).toEqual(10); +}); + +it('creates CellItem that returns error message on error', () => { + const props = { value: 10, error: { msg: 'Some error' }, isLoading: false, threshold: undefined }; + const item: CellItem = createCellItem(props, DefaultTableMessages); + + expect(`${item}`).toBe('Some error'); +}); + +it('creates CellItem that returns loading message on loading', () => { + const props = { value: 10, isLoading: true, threshold: undefined }; + const item: CellItem = createCellItem(props, DefaultTableMessages); - expect(`${item}`).toBe('Some error'); - }); + expect(`${item}`).toBe('Loading'); +}); - it('creates CellItem that returns loading message on loading', () => { - const props = { value: 10, isLoading: true, threshold: undefined }; - const item: CellItem = createCellItem(props); +it('accepts messageOverrides for loading message', () => { + const props = { value: 10, isLoading: true, threshold: undefined }; + const item: CellItem = createCellItem(props, messageOverride); - expect(`${item}`).toBe('Loading'); - }); + expect(`${item}`).toBe(messageOverride!.tableCell!.loading); }); diff --git a/packages/table/src/utils/createCellItem.ts b/packages/table/src/utils/createCellItem.ts index b687144c1..9aff3a057 100644 --- a/packages/table/src/utils/createCellItem.ts +++ b/packages/table/src/utils/createCellItem.ts @@ -1,20 +1,16 @@ -import { Primitive, Threshold } from '@synchro-charts/core'; -import { ErrorDetails } from '@iot-app-kit/core'; -import { CellItem } from './types'; +import { CellItem, CellProps } from './types'; +import { TableMessages } from './messages'; -type CellProps = { - value?: Primitive; - error?: ErrorDetails; - isLoading?: boolean; - threshold?: Threshold; -}; -export const createCellItem: (props?: CellProps) => CellItem = ({ value, error, isLoading, threshold } = {}) => { +export const createCellItem: (props: Partial, messageOverrides: TableMessages) => CellItem = ( + { value, error, isLoading, threshold } = {}, + messageOverrides +) => { const valueOf = () => { if (error) { return error.msg; } if (isLoading) { - return 'Loading'; + return messageOverrides.tableCell.loading; } return value; }; diff --git a/packages/table/src/utils/createTableItems.spec.ts b/packages/table/src/utils/createTableItems.spec.ts index 59fd299bd..699867994 100644 --- a/packages/table/src/utils/createTableItems.spec.ts +++ b/packages/table/src/utils/createTableItems.spec.ts @@ -1,6 +1,7 @@ import { DataStream, Viewport } from '@iot-app-kit/core'; import { Annotations, COMPARISON_OPERATOR, getThresholds } from '@synchro-charts/core'; import { createTableItems } from './createTableItems'; +import { DefaultTableMessages } from './messages'; const dataStreams: DataStream[] = [ { @@ -82,7 +83,7 @@ const itemWithRef = [ ]; it('creates table items', () => { - const items = createTableItems({ dataStreams, viewport, items: itemWithRef }); + const items = createTableItems({ dataStreams, viewport, items: itemWithRef }, DefaultTableMessages); expect(items).toMatchObject([ { value1: { value: 4 }, @@ -104,7 +105,7 @@ it('creates table items', () => { }); it('returns value as it is a primitive value', () => { - const items = createTableItems({ dataStreams, viewport, items: itemWithRef }); + const items = createTableItems({ dataStreams, viewport, items: itemWithRef }, DefaultTableMessages); const data = items[0].value1; expect((data as number) + 1).toBe(5); }); @@ -130,8 +131,8 @@ it('gets different data points on different viewports on the same data stream', }, }, ]; - const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }); - const items2 = createTableItems({ dataStreams, viewport: viewport2, items: itemDef }); + const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }, DefaultTableMessages); + const items2 = createTableItems({ dataStreams, viewport: viewport2, items: itemDef }, DefaultTableMessages); expect(items1).not.toEqual(items2); }); @@ -152,7 +153,7 @@ it('returns undefined value when no data points in data stream', () => { }, }, ]; - const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }); + const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }, DefaultTableMessages); expect(items1).toMatchObject([{ noDataPoints: { value: undefined } }]); }); @@ -198,12 +199,15 @@ it('contains breached threshold', () => { }, ]; - const tableItems = createTableItems({ - dataStreams, - viewport, - items, - thresholds: getThresholds(annotations), - }); + const tableItems = createTableItems( + { + dataStreams, + viewport, + items, + thresholds: getThresholds(annotations), + }, + DefaultTableMessages + ); const { itemOne, itemTwo, noRef } = tableItems[0]; diff --git a/packages/table/src/utils/createTableItems.ts b/packages/table/src/utils/createTableItems.ts index ab628fec9..486660546 100644 --- a/packages/table/src/utils/createTableItems.ts +++ b/packages/table/src/utils/createTableItems.ts @@ -4,13 +4,17 @@ import { getDataBeforeDate } from './dataFilters'; import { getDataPoints } from './getDataPoints'; import { CellItem, Item, ItemRef, TableItem } from './types'; import { createCellItem } from './createCellItem'; +import { TableMessages } from './messages'; -export const createTableItems: (config: { - dataStreams: DataStream[]; - items: Item[]; - viewport: Viewport; - thresholds?: Threshold[]; -}) => TableItem[] = ({ dataStreams, viewport, items, thresholds = [] }) => { +export const createTableItems: ( + config: { + dataStreams: DataStream[]; + items: Item[]; + viewport: Viewport; + thresholds?: Threshold[]; + }, + messageOverrides: TableMessages +) => TableItem[] = ({ dataStreams, viewport, items, thresholds = [] }, messageOverrides) => { return items.map((item) => { const keys = Object.keys(item); const keyDataPairs = keys.map<{ key: string; data: CellItem }>((key) => { @@ -32,7 +36,7 @@ export const createTableItems: (config: { thresholds, date: viewport.end, }); - return { key, data: createCellItem({ value, error, isLoading, threshold }) }; + return { key, data: createCellItem({ value, error, isLoading, threshold }, messageOverrides) }; } const value = dataPoints.slice(-1)[0]?.y; @@ -44,11 +48,11 @@ export const createTableItems: (config: { date: new Date(Date.now()), }); - return { key, data: createCellItem({ value, error, isLoading, threshold }) }; + return { key, data: createCellItem({ value, error, isLoading, threshold }, messageOverrides) }; } - return { key, data: createCellItem() }; + return { key, data: createCellItem({}, messageOverrides) }; } - return { key, data: createCellItem({ value: item[key] as Primitive }) }; + return { key, data: createCellItem({ value: item[key] as Primitive }, messageOverrides) }; }); return keyDataPairs.reduce( diff --git a/packages/table/src/utils/index.ts b/packages/table/src/utils/index.ts index 95efe0352..c1213df1a 100644 --- a/packages/table/src/utils/index.ts +++ b/packages/table/src/utils/index.ts @@ -1,2 +1,12 @@ export { createTableItems } from './createTableItems'; -export { CellItem, Item, ItemRef, TableItem, TableProps, ColumnDefinition, CellItemProps } from './types'; +export { + CellItem, + Item, + ItemRef, + TableItem, + TableProps, + ColumnDefinition, + CellItemProps, + RecursivePartial, +} from './types'; +export { TableMessages, DefaultTableMessages } from './messages'; diff --git a/packages/table/src/utils/messages.ts b/packages/table/src/utils/messages.ts new file mode 100644 index 000000000..93e631e63 --- /dev/null +++ b/packages/table/src/utils/messages.ts @@ -0,0 +1,51 @@ +import { PropertyFilterProps } from '@awsui/components-react'; + +export type PropertyFilterMessages = PropertyFilterProps.I18nStrings; +export type TableCellMessages = { + loading: string; +}; + +export type TableMessages = { + propertyFilter: PropertyFilterMessages; + tableCell: TableCellMessages; +}; + +export const DefaultTableCellMessages: TableCellMessages = { + loading: 'Loading', +}; + +export const DefaultPropertyFilterMessages: PropertyFilterMessages = { + filteringAriaLabel: 'your choice', + dismissAriaLabel: 'Dismiss', + filteringPlaceholder: 'Search', + groupValuesText: 'Values', + groupPropertiesText: 'Properties', + operatorsText: 'Operators', + operationAndText: 'and', + operationOrText: 'or', + operatorLessText: 'Less than', + operatorLessOrEqualText: 'Less than or equal', + operatorGreaterText: 'Greater than', + operatorGreaterOrEqualText: 'Greater than or equal', + operatorContainsText: 'Contains', + operatorDoesNotContainText: 'Does not contain', + operatorEqualsText: 'Equals', + operatorDoesNotEqualText: 'Does not equal', + editTokenHeader: 'Edit filter', + propertyText: 'Property', + operatorText: 'Operator', + valueText: 'Value', + cancelActionText: 'Cancel', + applyActionText: 'Apply', + allPropertiesLabel: 'All properties', + tokenLimitShowMore: 'Show more', + tokenLimitShowFewer: 'Show fewer', + clearFiltersText: 'Clear filters', + removeTokenButtonAriaLabel: () => 'Remove token', + enteredTextLabel: (text) => `Use: "${text}"`, +}; + +export const DefaultTableMessages: TableMessages = { + tableCell: DefaultTableCellMessages, + propertyFilter: DefaultPropertyFilterMessages, +}; diff --git a/packages/table/src/utils/tableHelpers.tsx b/packages/table/src/utils/tableHelpers.tsx index 88439c36b..ef3b3109b 100644 --- a/packages/table/src/utils/tableHelpers.tsx +++ b/packages/table/src/utils/tableHelpers.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PropertyFilterProps, TableProps as AWSUITableProps } from '@awsui/components-react'; +import { TableProps as AWSUITableProps } from '@awsui/components-react'; import { StatusIcon } from '@synchro-charts/core'; import { round } from '@iot-app-kit/core'; import { ColumnDefinition, TableItem } from './types'; @@ -13,7 +13,7 @@ export const getDefaultColumnDefinitions: ( return columnDefinitions.map((colDef) => ({ cell: (item: TableItem) => { if (!(colDef.key in item)) { - return
-
; + return '-'; } const { error, isLoading, value, threshold } = item[colDef.key]; @@ -55,34 +55,3 @@ export const getDefaultColumnDefinitions: ( id: colDef.id || colDef.key, })); }; - -export const defaultI18nStrings: PropertyFilterProps.I18nStrings = { - filteringAriaLabel: 'your choice', - dismissAriaLabel: 'Dismiss', - filteringPlaceholder: 'Search', - groupValuesText: 'Values', - groupPropertiesText: 'Properties', - operatorsText: 'Operators', - operationAndText: 'and', - operationOrText: 'or', - operatorLessText: 'Less than', - operatorLessOrEqualText: 'Less than or equal', - operatorGreaterText: 'Greater than', - operatorGreaterOrEqualText: 'Greater than or equal', - operatorContainsText: 'Contains', - operatorDoesNotContainText: 'Does not contain', - operatorEqualsText: 'Equals', - operatorDoesNotEqualText: 'Does not equal', - editTokenHeader: 'Edit filter', - propertyText: 'Property', - operatorText: 'Operator', - valueText: 'Value', - cancelActionText: 'Cancel', - applyActionText: 'Apply', - allPropertiesLabel: 'All properties', - tokenLimitShowMore: 'Show more', - tokenLimitShowFewer: 'Show fewer', - clearFiltersText: 'Clear filters', - removeTokenButtonAriaLabel: () => 'Remove token', - enteredTextLabel: (text) => `Use: "${text}"`, -}; diff --git a/packages/table/src/utils/types.ts b/packages/table/src/utils/types.ts index 7ed5b61dd..ab64bec59 100644 --- a/packages/table/src/utils/types.ts +++ b/packages/table/src/utils/types.ts @@ -2,6 +2,7 @@ import { Annotations, Primitive, Threshold } from '@synchro-charts/core'; import { ErrorDetails } from '@iot-app-kit/core'; import { TableProps as AWSUITableProps } from '@awsui/components-react'; import { UseCollectionOptions } from '@awsui/collection-hooks/dist/cjs/interfaces'; +import { TableMessages } from './messages'; export type ItemRef = { $cellRef: { @@ -37,9 +38,25 @@ export interface ColumnDefinition extends Omit, 'columnDefinitions'> { annotations?: Annotations; columnDefinitions: ColumnDefinition[]; sorting?: UseCollectionOptions['sorting']; propertyFiltering?: UseCollectionOptions['propertyFiltering']; + messageOverrides: TableMessages; } + +export type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +};