From f99bcde75f3fad7dac82ac657f1a2aca8dbbbc4c Mon Sep 17 00:00:00 2001 From: Jason Buss Date: Tue, 23 May 2023 14:15:09 -0600 Subject: [PATCH] feat(dashboard): refactor asset description sdk calls to use tanstack --- package-lock.json | 210 +++++++++++++++++- packages/dashboard/package.json | 3 + .../src/components/dashboard/index.tsx | 26 ++- .../src/components/palette/index.test.tsx | 33 ++- .../components/panel.test.tsx | 19 +- .../resourceExplorer/components/panel.tsx | 38 ++-- .../resourceExplorer/stencil/index.tsx | 3 +- .../propertiesAlarmSection/index.test.tsx | 17 +- .../sections/propertiesAlarmSection/index.tsx | 8 +- packages/dashboard/src/data/query-client.ts | 13 ++ .../src/hooks/useAssetDescriptionMapAsync.ts | 60 ----- .../src/hooks/useAssetDescriptionQueries.ts | 58 +++++ 12 files changed, 371 insertions(+), 117 deletions(-) create mode 100644 packages/dashboard/src/data/query-client.ts delete mode 100644 packages/dashboard/src/hooks/useAssetDescriptionMapAsync.ts create mode 100644 packages/dashboard/src/hooks/useAssetDescriptionQueries.ts diff --git a/package-lock.json b/package-lock.json index cbb09b2c5..9c7cb4c93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "8.8.0", - "eslint-config-turbo": "*", + "eslint-config-turbo": "latest", "eslint-plugin-formatjs": "4.10.1", "eslint-plugin-import": "2.27.5", "eslint-plugin-jest": "^27.2.1", @@ -26227,6 +26227,87 @@ "uuid": "bin/uuid" } }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-4.29.4.tgz", + "integrity": "sha512-dKWigue+8KV9BaKLq5yUu4wUYCendpvEve8lc3YyD634MQJ/joK7nJXiMfAEHY5Bwnh00Y3ZTBJ6YNtAz4CbVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dev": true, + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, + "node_modules/@tanstack/query-core": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz", + "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz", + "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==", + "dependencies": { + "@tanstack/query-core": "4.29.5", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "4.29.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.6.tgz", + "integrity": "sha512-qpYI41a69MWmrllcGiSE1KlpmnwJY/w0yKMnmp6VXn7nVy0i5TMMAT4u8D48F1Ipv/BKIDI1lqxPAvB4MqryBg==", + "dev": true, + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.29.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz", @@ -31972,6 +32053,21 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-anything": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.4.tgz", + "integrity": "sha512-MaQ9FwzlZ/KLeVCLhzI3rZw0EhrIryfZa3AyT4agVybR0DjlkDHA8898lamLD6kfkf9MMn8D+zDAUR4+GxaymQ==", + "dev": true, + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -40053,6 +40149,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.9.tgz", + "integrity": "sha512-I3FU0rkVvwhgLLEs6iITwZ/JaLXe7tQcHyzupXky8jigt1vu4KM0UOqDr963j36JRvJ835EATVIm6MnGz/i1/g==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -56631,6 +56739,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==", + "dev": true + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", @@ -60199,6 +60313,18 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, + "node_modules/superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "dev": true, + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -63231,7 +63357,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "dev": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -65557,6 +65682,7 @@ "@iot-app-kit/react-components": "5.8.1", "@iot-app-kit/source-iotsitewise": "5.8.1", "@popperjs/core": "^2.11.7", + "@tanstack/react-query": "^4.29.5", "buffer": "^6.0.3", "is-hotkey": "^0.2.0", "parse-duration": "^1.0.3", @@ -65583,6 +65709,8 @@ "@storybook/manager-webpack5": "^6.5.16", "@storybook/react": "^6.5.16", "@storybook/testing-library": "^0.1.0", + "@tanstack/eslint-plugin-query": "^4.29.0", + "@tanstack/react-query-devtools": "^4.29.5", "@testing-library/dom": "^9.3.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", @@ -78674,6 +78802,9 @@ "@storybook/manager-webpack5": "^6.5.16", "@storybook/react": "^6.5.16", "@storybook/testing-library": "^0.1.0", + "@tanstack/eslint-plugin-query": "^4.29.0", + "@tanstack/react-query": "^4.29.5", + "@tanstack/react-query-devtools": "^4.29.5", "@testing-library/dom": "^9.3.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", @@ -91015,6 +91146,46 @@ } } }, + "@tanstack/eslint-plugin-query": { + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-4.29.4.tgz", + "integrity": "sha512-dKWigue+8KV9BaKLq5yUu4wUYCendpvEve8lc3YyD634MQJ/joK7nJXiMfAEHY5Bwnh00Y3ZTBJ6YNtAz4CbVQ==", + "dev": true + }, + "@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dev": true, + "requires": { + "remove-accents": "0.4.2" + } + }, + "@tanstack/query-core": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz", + "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==" + }, + "@tanstack/react-query": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz", + "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==", + "requires": { + "@tanstack/query-core": "4.29.5", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.29.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.6.tgz", + "integrity": "sha512-qpYI41a69MWmrllcGiSE1KlpmnwJY/w0yKMnmp6VXn7nVy0i5TMMAT4u8D48F1Ipv/BKIDI1lqxPAvB4MqryBg==", + "dev": true, + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + } + }, "@testing-library/dom": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz", @@ -95498,6 +95669,15 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-anything": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.4.tgz", + "integrity": "sha512-MaQ9FwzlZ/KLeVCLhzI3rZw0EhrIryfZa3AyT4agVybR0DjlkDHA8898lamLD6kfkf9MMn8D+zDAUR4+GxaymQ==", + "dev": true, + "requires": { + "is-what": "^4.1.8" + } + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -97925,7 +98105,7 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "8.8.0", - "eslint-config-turbo": "*", + "eslint-config-turbo": "latest", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-formatjs": "4.10.1", "eslint-plugin-import": "2.27.5", @@ -101327,6 +101507,12 @@ "get-intrinsic": "^1.1.1" } }, + "is-what": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.9.tgz", + "integrity": "sha512-I3FU0rkVvwhgLLEs6iITwZ/JaLXe7tQcHyzupXky8jigt1vu4KM0UOqDr963j36JRvJ835EATVIm6MnGz/i1/g==", + "dev": true + }, "is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -113187,6 +113373,12 @@ "mdast-squeeze-paragraphs": "^4.0.0" } }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", @@ -115834,6 +116026,15 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, + "superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "dev": true, + "requires": { + "copy-anything": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -117982,8 +118183,7 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "dev": true + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, "util": { "version": "0.11.1", diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 37ea52dd8..dae1f07ca 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -54,6 +54,8 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", + "@tanstack/eslint-plugin-query": "^4.29.0", + "@tanstack/react-query-devtools": "^4.29.5", "@types/is-hotkey": "^0.1.7", "@types/lodash": "^4.14.191", "@types/node": "^18.16.14", @@ -101,6 +103,7 @@ "@iot-app-kit/react-components": "5.8.1", "@iot-app-kit/source-iotsitewise": "5.8.1", "@popperjs/core": "^2.11.7", + "@tanstack/react-query": "^4.29.5", "buffer": "^6.0.3", "is-hotkey": "^0.2.0", "parse-duration": "^1.0.3", diff --git a/packages/dashboard/src/components/dashboard/index.tsx b/packages/dashboard/src/components/dashboard/index.tsx index f45f82d8d..54a6d026b 100644 --- a/packages/dashboard/src/components/dashboard/index.tsx +++ b/packages/dashboard/src/components/dashboard/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { DndProvider } from 'react-dnd'; import { TouchBackend } from 'react-dnd-touch-backend'; +import { QueryClientProvider } from '@tanstack/react-query'; import InternalDashboard from '../internalDashboard'; @@ -17,6 +18,7 @@ import { getQueries } from './getQueries'; import '@cloudscape-design/global-styles/index.css'; import '../../styles/variables.css'; +import { queryClient } from '~/data/query-client'; setupDashboardPlugins(plugins); @@ -37,17 +39,19 @@ const Dashboard: React.FC = ({ return ( - - - - - + + + + + + + ); diff --git a/packages/dashboard/src/components/palette/index.test.tsx b/packages/dashboard/src/components/palette/index.test.tsx index 96c766438..93028310e 100644 --- a/packages/dashboard/src/components/palette/index.test.tsx +++ b/packages/dashboard/src/components/palette/index.test.tsx @@ -13,6 +13,15 @@ import { setupDashboardPlugins } from '../../customization/api'; import pluginsConfiguration from '../../customization/pluginsConfiguration'; import type { DashboardState } from '../../store/state'; import type { RecursivePartial } from '~/types'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const testQueryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); const renderDashboard = (state?: RecursivePartial) => { setupDashboardPlugins(pluginsConfiguration); @@ -20,17 +29,19 @@ const renderDashboard = (state?: RecursivePartial) => { const store = configureDashboardStore(state); const renderResults = render( - - - - - + + + + + + + ); return { ...renderResults, store }; diff --git a/packages/dashboard/src/components/resourceExplorer/components/panel.test.tsx b/packages/dashboard/src/components/resourceExplorer/components/panel.test.tsx index 8b8c3fde4..b81a44a12 100644 --- a/packages/dashboard/src/components/resourceExplorer/components/panel.test.tsx +++ b/packages/dashboard/src/components/resourceExplorer/components/panel.test.tsx @@ -2,16 +2,25 @@ import * as React from 'react'; import { render } from '@testing-library/react'; import { wrapWithTestBackend } from 'react-dnd-test-utils'; import { ResourceExplorerPanel } from './panel'; -import type { DashboardMessages } from '~/messages'; - -const mockMessageOverrides = { resourceExplorer: { panelEmptyLabel: 'Empty panel' } } as DashboardMessages; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const [PanelContext] = wrapWithTestBackend(ResourceExplorerPanel); +const testQueryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); describe('ResourceExplorerPanel', () => { it('renders empty panel message when no items passed', () => { - const { getByText } = render(); + const { getByText } = render( + + + + ); - expect(getByText('Empty panel')).toBeTruthy(); + expect(getByText('Asset has no properties or child assets.')).toBeTruthy(); }); }); diff --git a/packages/dashboard/src/components/resourceExplorer/components/panel.tsx b/packages/dashboard/src/components/resourceExplorer/components/panel.tsx index c4588059d..7761dc051 100644 --- a/packages/dashboard/src/components/resourceExplorer/components/panel.tsx +++ b/packages/dashboard/src/components/resourceExplorer/components/panel.tsx @@ -6,14 +6,16 @@ import Icon from '@cloudscape-design/components/icon'; import { ItemTypes } from '../../dragLayer/itemTypes'; import './style.css'; -import { useAssetDescriptionAsync } from '~/hooks/useAssetDescriptionMapAsync'; +import { useAssetDescriptionQuery } from '~/hooks/useAssetDescriptionQueries'; import type { AlarmSummary, AssetSummary, PropertySummary } from './mapper'; -import { mapAssetDescriptionToAssetSummary } from './mapper'; -import type { DashboardMessages } from '~/messages'; + +const defaultMessages = { + tableEmpty: 'Asset has no properties or child assets.', + assetQueryFailed: 'Failed to load assets or properties.', +}; export interface ResourceExplorerPanelProps { assetId: string | undefined; - messageOverrides: DashboardMessages; } type PanelSummary = { @@ -44,9 +46,9 @@ export const ResourceExplorerPanelAssetPropertyDragGhost = ({ item }: { item: Pa ); }; -const PanelEmpty = ({ messageOverrides }: { messageOverrides: DashboardMessages }) => ( +const PanelEmpty = ({ emptyMessage }: { emptyMessage: string }) => ( - {messageOverrides.resourceExplorer.panelEmptyLabel} + {emptyMessage} ); @@ -59,7 +61,7 @@ const Asset: React.FC = (item) => { assetSummary: item.assetSummary, }, }; - }); + }, [JSON.stringify(item)]); return (
@@ -109,15 +111,15 @@ const mapPanelAssetSummary = }, }); -export const ResourceExplorerPanel: React.FC = ({ - assetId: currentAssetId, - messageOverrides, -}) => { - const describedAsset = useAssetDescriptionAsync(currentAssetId); +export const ResourceExplorerPanel: React.FC = ({ assetId: currentAssetId }) => { + const assetDescriptionQuery = useAssetDescriptionQuery(currentAssetId); - const { assetId, assetName, properties, alarms } = describedAsset - ? mapAssetDescriptionToAssetSummary(describedAsset) - : { properties: [], alarms: [], assetId: '', assetName: '' }; + const { assetId, assetName, properties, alarms } = assetDescriptionQuery.data ?? { + properties: [], + alarms: [], + assetId: '', + assetName: '', + }; const mapper = mapPanelAssetSummary(assetId || '', assetName || ''); @@ -145,7 +147,11 @@ export const ResourceExplorerPanel: React.FC = ({ columnDefinitions={tableColumnDefinitions} items={items} trackBy='name' - empty={} + empty={ + + } /> ); }; diff --git a/packages/dashboard/src/components/resourceExplorer/stencil/index.tsx b/packages/dashboard/src/components/resourceExplorer/stencil/index.tsx index 56e6e06a5..e5f03245a 100644 --- a/packages/dashboard/src/components/resourceExplorer/stencil/index.tsx +++ b/packages/dashboard/src/components/resourceExplorer/stencil/index.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { ResourceExplorer } from '@iot-app-kit/react-components'; import { ResourceExplorerPanel } from '../components/panel'; -import { DefaultDashboardMessages } from '../../../messages'; import type { SiteWiseQuery } from '@iot-app-kit/source-iotsitewise'; import type { AssetSummary, DescribeAssetResponse } from '@aws-sdk/client-iotsitewise'; import type { NonCancelableCustomEvent, TableProps } from '@cloudscape-design/components'; @@ -44,7 +43,7 @@ export const StencilResourceExplorer: React.FC = (
- +
); diff --git a/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.test.tsx b/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.test.tsx index 760a966bd..d3a4761e6 100644 --- a/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.test.tsx +++ b/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.test.tsx @@ -15,6 +15,7 @@ import { mockAssetDescription } from '../../../../../testing/mocks/siteWiseSDK'; import type { QueryWidget } from '../../../../customization/widgets/types'; import type { DashboardState } from '../../../../store/state'; import { DashboardIotSiteWiseClients } from '~/types'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const MockAssetQuery: SiteWiseAssetQuery['assets'][number] = { assetId: 'mock-id', @@ -53,6 +54,14 @@ const state: Partial = { selectedWidgets: [MockWidget], }; +const testQueryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + const renderPropertiesAndAlarmsSectionAsync = async () => { const describeAsset = jest.fn().mockImplementation(() => Promise.resolve(mockAssetDescription)); @@ -64,9 +73,11 @@ const renderPropertiesAndAlarmsSectionAsync = async () => { await act(async () => { render( - - - + + + + + ); }); diff --git a/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.tsx b/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.tsx index 7e8571829..03a86273a 100644 --- a/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.tsx +++ b/packages/dashboard/src/components/sidePanel/sections/propertiesAlarmSection/index.tsx @@ -1,11 +1,10 @@ import type { FC } from 'react'; import React from 'react'; import { ExpandableSection, SpaceBetween } from '@cloudscape-design/components'; -import { useAssetDescriptionMapAsync } from '~/hooks/useAssetDescriptionMapAsync'; +import { useAssetDescriptionMapQuery } from '~/hooks/useAssetDescriptionQueries'; import ExpandableSectionHeader from '../../shared/expandableSectionHeader'; import { PropertyComponent } from './propertyComponent'; import { useWidgetLense } from '../../utils/useWidgetLense'; -import { mapAssetDescriptionToAssetSummary } from '~/components/resourceExplorer/components/mapper'; import type { SiteWiseAssetQuery } from '@iot-app-kit/source-iotsitewise'; import { toId } from '@iot-app-kit/source-iotsitewise'; import type { QueryWidget, TableProperties } from '~/customization/widgets/types'; @@ -82,7 +81,8 @@ const GeneralPropertiesAlarmsSection: FC = ({ }) ); - const describedAssetsMap = useAssetDescriptionMapAsync(siteWiseAssetQuery); + const describedAssetsMapQuery = useAssetDescriptionMapQuery(siteWiseAssetQuery); + const describedAssetsMap = describedAssetsMapQuery.data ?? {}; const onUpdatePropertyColor = (refId: string) => (color: string) => { updateStyleSettings({ @@ -101,7 +101,7 @@ const GeneralPropertiesAlarmsSection: FC = ({ key={`${assetId}-${propertyId}`} propertyId={propertyId} refId={refId} - assetSummary={mapAssetDescriptionToAssetSummary(describedAssetsMap[assetId])} + assetSummary={describedAssetsMap[assetId]} styleSettings={styleSettings} onDeleteAssetQuery={onDeleteAssetQuery({ assetId, propertyId, siteWiseAssetQuery, updateSiteWiseAssetQuery })} onUpdatePropertyColor={onUpdatePropertyColor(refId)} diff --git a/packages/dashboard/src/data/query-client.ts b/packages/dashboard/src/data/query-client.ts new file mode 100644 index 000000000..11cfe6379 --- /dev/null +++ b/packages/dashboard/src/data/query-client.ts @@ -0,0 +1,13 @@ +import { QueryCache, QueryClient } from '@tanstack/react-query'; + +// 10 minutes +const STALE_TIME = 1000 * 60 * 10; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: STALE_TIME, + }, + }, + queryCache: new QueryCache({}), +}); diff --git a/packages/dashboard/src/hooks/useAssetDescriptionMapAsync.ts b/packages/dashboard/src/hooks/useAssetDescriptionMapAsync.ts deleted file mode 100644 index db942479a..000000000 --- a/packages/dashboard/src/hooks/useAssetDescriptionMapAsync.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { DescribeAssetCommand, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { useEffect, useState } from 'react'; -import { useClients } from '~/components/dashboard/clientContext'; -import type { DescribeAssetResponse } from '@aws-sdk/client-iotsitewise'; -import type { SiteWiseAssetQuery } from '@iot-app-kit/source-iotsitewise'; - -const describeAsset = (client: IoTSiteWiseClient, assetId: string) => - client.send(new DescribeAssetCommand({ assetId })); - -export const useAssetDescriptionMapAsync = ( - siteWiseAssetQuery: SiteWiseAssetQuery | undefined -): Record => { - const [describedAssets, setDescribedAssets] = useState>({}); - const { iotSiteWiseClient } = useClients(); - - const fetchAssetDescriptions = async () => { - if (!iotSiteWiseClient || !siteWiseAssetQuery) return; - const describedAssetPromises = siteWiseAssetQuery.assets.map(({ assetId }) => - describeAsset(iotSiteWiseClient, assetId) - ); - - const describedAssetsList = await Promise.all(describedAssetPromises); - const map = describedAssetsList.reduce((acc, n) => { - const { assetId } = n; - if (assetId) { - acc[assetId] = n; - } - return acc; - }, {} as Record); - setDescribedAssets(map); - }; - - useEffect(() => { - fetchAssetDescriptions(); - }, [JSON.stringify(siteWiseAssetQuery)]); - - return describedAssets; -}; - -export const useAssetDescriptionAsync = (assetId: string | undefined) => { - const [describedAsset, setDescribedAsset] = useState(undefined); - const { iotSiteWiseClient } = useClients(); - - const fetchAssetDescription = async () => { - if (!iotSiteWiseClient || !assetId) return; - - try { - setDescribedAsset(await describeAsset(iotSiteWiseClient, assetId)); - } catch (e) { - console.log(e); - } - }; - - useEffect(() => { - setDescribedAsset(undefined); - fetchAssetDescription(); - }, [assetId]); - - return describedAsset; -}; diff --git a/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts b/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts new file mode 100644 index 000000000..8349441c6 --- /dev/null +++ b/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts @@ -0,0 +1,58 @@ +import { useQuery } from '@tanstack/react-query'; + +import { DescribeAssetCommand, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { useClients } from '~/components/dashboard/clientContext'; +import type { SiteWiseAssetQuery } from '@iot-app-kit/source-iotsitewise'; +import { AssetSummary, mapAssetDescriptionToAssetSummary } from '~/components/resourceExplorer/components/mapper'; + +const describeAsset = (client: IoTSiteWiseClient, assetId: string) => + client.send(new DescribeAssetCommand({ assetId })); + +const describeSiteWiseAssetQuery = async (client: IoTSiteWiseClient, siteWiseAssetQuery: SiteWiseAssetQuery) => + Promise.all(siteWiseAssetQuery.assets.map(({ assetId }) => describeAsset(client, assetId))); + +const ASSET_DESCRIPTION_QUERY_KEY = ['assetDescriptions']; + +export const useAssetDescriptionMapQuery = (siteWiseAssetQuery: SiteWiseAssetQuery | undefined) => { + const { iotSiteWiseClient } = useClients(); + + const fetchSiteWiseAssetQueryDescription = async () => { + if (!iotSiteWiseClient || !siteWiseAssetQuery) return; + + return describeSiteWiseAssetQuery(iotSiteWiseClient, siteWiseAssetQuery); + }; + + return useQuery({ + queryKey: [ + ...ASSET_DESCRIPTION_QUERY_KEY, + 'assetDescriptionsMap', + [...(siteWiseAssetQuery?.assets.map((a) => a.assetId) ?? [])], + ], + queryFn: () => fetchSiteWiseAssetQueryDescription(), + select: (data) => + data?.reduce>((acc, n) => { + const { assetId } = n; + if (assetId) { + acc[assetId] = mapAssetDescriptionToAssetSummary(n); + } + return acc; + }, {}) ?? {}, + }); +}; + +export const useAssetDescriptionQuery = (assetId: string | undefined) => { + const { iotSiteWiseClient } = useClients(); + + const fetchAssetDescription = async () => { + if (!iotSiteWiseClient || !assetId) return; + + return describeAsset(iotSiteWiseClient, assetId); + }; + + return useQuery({ + queryKey: [...ASSET_DESCRIPTION_QUERY_KEY, assetId], + queryFn: () => fetchAssetDescription(), + select: (data) => (data ? mapAssetDescriptionToAssetSummary(data) : data), + enabled: !!assetId, + }); +};