From f0e4a7b16d2c93a837a8497d0c87f218bc37c8a1 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Tue, 14 Mar 2023 15:18:30 +0100 Subject: [PATCH 01/12] feat: move inline svgs to icon components --- dashboard/components/icons/AlertIcon.tsx | 34 ++ dashboard/components/icons/BookmarkIcon.tsx | 27 ++ .../components/icons/ChevronDownIcon.tsx | 21 ++ dashboard/components/icons/DeleteIcon.tsx | 20 + dashboard/components/icons/DownloadIcon.tsx | 34 ++ dashboard/components/icons/DuplicateIcon.tsx | 27 ++ dashboard/components/icons/EditIcon.tsx | 28 ++ dashboard/components/icons/LinkIcon.tsx | 27 ++ dashboard/components/icons/WarningIcon.tsx | 20 + .../components/InventoryViewsHeader.tsx | 342 ------------------ .../components/view/InventoryView.tsx | 59 +-- .../components/view/InventoryViewHeader.tsx | 228 ++++++++++++ 12 files changed, 472 insertions(+), 395 deletions(-) create mode 100644 dashboard/components/icons/AlertIcon.tsx create mode 100644 dashboard/components/icons/BookmarkIcon.tsx create mode 100644 dashboard/components/icons/ChevronDownIcon.tsx create mode 100644 dashboard/components/icons/DeleteIcon.tsx create mode 100644 dashboard/components/icons/DownloadIcon.tsx create mode 100644 dashboard/components/icons/DuplicateIcon.tsx create mode 100644 dashboard/components/icons/EditIcon.tsx create mode 100644 dashboard/components/icons/LinkIcon.tsx create mode 100644 dashboard/components/icons/WarningIcon.tsx delete mode 100644 dashboard/components/inventory/components/InventoryViewsHeader.tsx create mode 100644 dashboard/components/inventory/components/view/InventoryViewHeader.tsx diff --git a/dashboard/components/icons/AlertIcon.tsx b/dashboard/components/icons/AlertIcon.tsx new file mode 100644 index 000000000..f727e6321 --- /dev/null +++ b/dashboard/components/icons/AlertIcon.tsx @@ -0,0 +1,34 @@ +import { SVGProps } from 'react'; + +const AlertIcon = (props: SVGProps) => ( + + + + + +); + +export default AlertIcon; diff --git a/dashboard/components/icons/BookmarkIcon.tsx b/dashboard/components/icons/BookmarkIcon.tsx new file mode 100644 index 000000000..0d17f7c24 --- /dev/null +++ b/dashboard/components/icons/BookmarkIcon.tsx @@ -0,0 +1,27 @@ +import { SVGProps } from 'react'; + +const BookmarkIcon = (props: SVGProps) => ( + + + + +); + +export default BookmarkIcon; diff --git a/dashboard/components/icons/ChevronDownIcon.tsx b/dashboard/components/icons/ChevronDownIcon.tsx new file mode 100644 index 000000000..3fd7b8d3a --- /dev/null +++ b/dashboard/components/icons/ChevronDownIcon.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from 'react'; + +const ChevronDownIcon = (props: SVGProps) => ( + + + +); + +export default ChevronDownIcon; diff --git a/dashboard/components/icons/DeleteIcon.tsx b/dashboard/components/icons/DeleteIcon.tsx new file mode 100644 index 000000000..973ea4760 --- /dev/null +++ b/dashboard/components/icons/DeleteIcon.tsx @@ -0,0 +1,20 @@ +import { SVGProps } from 'react'; + +const DeleteIcon = (props: SVGProps) => ( + + + +); + +export default DeleteIcon; diff --git a/dashboard/components/icons/DownloadIcon.tsx b/dashboard/components/icons/DownloadIcon.tsx new file mode 100644 index 000000000..c6af486e4 --- /dev/null +++ b/dashboard/components/icons/DownloadIcon.tsx @@ -0,0 +1,34 @@ +import { SVGProps } from 'react'; + +const DownloadIcon = (props: SVGProps) => ( + + + + + +); + +export default DownloadIcon; diff --git a/dashboard/components/icons/DuplicateIcon.tsx b/dashboard/components/icons/DuplicateIcon.tsx new file mode 100644 index 000000000..f5b0db259 --- /dev/null +++ b/dashboard/components/icons/DuplicateIcon.tsx @@ -0,0 +1,27 @@ +import { SVGProps } from 'react'; + +const DuplicateIcon = (props: SVGProps) => ( + + + + +); + +export default DuplicateIcon; diff --git a/dashboard/components/icons/EditIcon.tsx b/dashboard/components/icons/EditIcon.tsx new file mode 100644 index 000000000..3304d2ac1 --- /dev/null +++ b/dashboard/components/icons/EditIcon.tsx @@ -0,0 +1,28 @@ +import { SVGProps } from 'react'; + +const EditIcon = (props: SVGProps) => ( + + + + +); + +export default EditIcon; diff --git a/dashboard/components/icons/LinkIcon.tsx b/dashboard/components/icons/LinkIcon.tsx new file mode 100644 index 000000000..1162a93b4 --- /dev/null +++ b/dashboard/components/icons/LinkIcon.tsx @@ -0,0 +1,27 @@ +import { SVGProps } from 'react'; + +const LinkIcon = (props: SVGProps) => ( + + + + +); + +export default LinkIcon; diff --git a/dashboard/components/icons/WarningIcon.tsx b/dashboard/components/icons/WarningIcon.tsx new file mode 100644 index 000000000..e59a43683 --- /dev/null +++ b/dashboard/components/icons/WarningIcon.tsx @@ -0,0 +1,20 @@ +import { SVGProps } from 'react'; + +const WarningIcon = (props: SVGProps) => ( + + + +); + +export default WarningIcon; diff --git a/dashboard/components/inventory/components/InventoryViewsHeader.tsx b/dashboard/components/inventory/components/InventoryViewsHeader.tsx deleted file mode 100644 index 6e92c8e4c..000000000 --- a/dashboard/components/inventory/components/InventoryViewsHeader.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import { NextRouter } from 'next/router'; -import { FormEvent, useState } from 'react'; -import Button from '../../button/Button'; -import Modal from '../../modal/Modal'; -import { ToastProps } from '../../toast/hooks/useToast'; -import { - InventoryFilterData, - View -} from '../hooks/useInventory/types/useInventoryTypes'; -import { ViewsPages } from './view/hooks/useViews'; - -type InventoryViewsHeaderProps = { - openModal: ( - filters?: InventoryFilterData[], - openPage?: ViewsPages | undefined - ) => void; - views: View[] | undefined; - router: NextRouter; - saveView: ( - e: FormEvent, - duplicate?: boolean | undefined, - viewToBeDuplicated?: View | undefined - ) => void; - loading: boolean; - deleteLoading: boolean; - deleteView: ( - dropdown?: boolean | undefined, - viewToBeDeleted?: View | undefined - ) => void; - setToast: (toast: ToastProps | undefined) => void; -}; - -function InventoryViewsHeader({ - openModal, - views, - router, - saveView, - loading, - deleteView, - deleteLoading, - setToast -}: InventoryViewsHeaderProps) { - const [dropdownIsOpen, setDropdownIsOpen] = useState(false); - const [modalIsOpen, setModalIsOpen] = useState(false); - - function closeDropdown() { - setDropdownIsOpen(false); - } - - function openDropdown() { - setDropdownIsOpen(true); - } - - function closeDoubleConfirmationModal() { - setModalIsOpen(false); - } - - function openDoubleConfirmationModal() { - setModalIsOpen(true); - } - - const currentView = views?.find( - view => view.id.toString() === router.query.view - ); - - return ( -
- {currentView && ( -
- {currentView.name} - -
- )} - - {dropdownIsOpen && ( - <> -
-
-
- - - - - - -
-
- - )} - - -
-
-
- - - -
-
-

- Are you sure you want to delete this view? -

-

- This is a permanent action. -

-
-
-
- - -
-
-
-
- ); -} - -export default InventoryViewsHeader; diff --git a/dashboard/components/inventory/components/view/InventoryView.tsx b/dashboard/components/inventory/components/view/InventoryView.tsx index ee1208114..4491c4d9c 100644 --- a/dashboard/components/inventory/components/view/InventoryView.tsx +++ b/dashboard/components/inventory/components/view/InventoryView.tsx @@ -4,6 +4,8 @@ import formatNumber from '../../../../utils/formatNumber'; import providers, { Provider } from '../../../../utils/providerHelper'; import Button from '../../../button/Button'; import Checkbox from '../../../checkbox/Checkbox'; +import AlertIcon from '../../../icons/AlertIcon'; +import BookmarkIcon from '../../../icons/BookmarkIcon'; import Input from '../../../input/Input'; import Sidepanel from '../../../sidepanel/Sidepanel'; import SidepanelHeader from '../../../sidepanel/SidepanelHeader'; @@ -17,9 +19,9 @@ import { View } from '../../hooks/useInventory/types/useInventoryTypes'; import InventoryFilterSummary from '../filter/InventoryFilterSummary'; -import InventoryViewsHeader from '../InventoryViewsHeader'; import InventoryViewAlerts from './alerts/InventoryViewAlerts'; import useViews from './hooks/useViews'; +import InventoryViewHeader from './InventoryViewHeader'; type InventoryViewProps = { filters: InventoryFilterData[] | undefined; @@ -72,7 +74,7 @@ function InventoryView({ return ( <> - - - - - - + Alerts @@ -133,28 +107,7 @@ function InventoryView({ {/* Save as a view button */} {!router.query.view && ( )} diff --git a/dashboard/components/inventory/components/view/InventoryViewHeader.tsx b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx new file mode 100644 index 000000000..2d28c00a8 --- /dev/null +++ b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx @@ -0,0 +1,228 @@ +import { NextRouter } from 'next/router'; +import { FormEvent, useState } from 'react'; +import Button from '../../../button/Button'; +import AlertIcon from '../../../icons/AlertIcon'; +import ChevronDownIcon from '../../../icons/ChevronDownIcon'; +import DeleteIcon from '../../../icons/DeleteIcon'; +import DownloadIcon from '../../../icons/DownloadIcon'; +import DuplicateIcon from '../../../icons/DuplicateIcon'; +import EditIcon from '../../../icons/EditIcon'; +import LinkIcon from '../../../icons/LinkIcon'; +import WarningIcon from '../../../icons/WarningIcon'; +import Modal from '../../../modal/Modal'; +import { ToastProps } from '../../../toast/hooks/useToast'; +import { + InventoryFilterData, + View +} from '../../hooks/useInventory/types/useInventoryTypes'; +import { ViewsPages } from './hooks/useViews'; + +type InventoryViewHeaderProps = { + openModal: ( + filters?: InventoryFilterData[], + openPage?: ViewsPages | undefined + ) => void; + views: View[] | undefined; + router: NextRouter; + saveView: ( + e: FormEvent, + duplicate?: boolean | undefined, + viewToBeDuplicated?: View | undefined + ) => void; + loading: boolean; + deleteLoading: boolean; + deleteView: ( + dropdown?: boolean | undefined, + viewToBeDeleted?: View | undefined + ) => void; + setToast: (toast: ToastProps | undefined) => void; +}; + +function InventoryViewHeader({ + openModal, + views, + router, + saveView, + loading, + deleteView, + deleteLoading, + setToast +}: InventoryViewHeaderProps) { + const [dropdownIsOpen, setDropdownIsOpen] = useState(false); + const [modalIsOpen, setModalIsOpen] = useState(false); + + function closeDropdown() { + setDropdownIsOpen(false); + } + + function openDropdown() { + setDropdownIsOpen(true); + } + + function closeDoubleConfirmationModal() { + setModalIsOpen(false); + } + + function openDoubleConfirmationModal() { + setModalIsOpen(true); + } + + const currentView = views?.find( + view => view.id.toString() === router.query.view + ); + + return ( +
+ {currentView && ( +
+ {currentView.name} + +
+ )} + + {dropdownIsOpen && ( + <> +
+
+
+ + + + + + + +
+
+ + )} + + +
+
+
+ +
+
+

+ Are you sure you want to delete this view? +

+

+ This is a permanent action. +

+
+
+
+ + +
+
+
+
+ ); +} + +export default InventoryViewHeader; From b2914ae93750f34770c79e1ff220d4495f91a2ea Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Tue, 14 Mar 2023 15:44:12 +0100 Subject: [PATCH 02/12] feat: add export CSV component --- dashboard/components/export-csv/ExportCSV.tsx | 16 + .../components/export-csv/ExportCSVButton.tsx | 27 ++ .../components/export-csv/useExportCSV.tsx | 42 +++ .../components/view/InventoryViewHeader.tsx | 277 +++++++++--------- dashboard/services/settingsService.ts | 13 + 5 files changed, 232 insertions(+), 143 deletions(-) create mode 100644 dashboard/components/export-csv/ExportCSV.tsx create mode 100644 dashboard/components/export-csv/ExportCSVButton.tsx create mode 100644 dashboard/components/export-csv/useExportCSV.tsx diff --git a/dashboard/components/export-csv/ExportCSV.tsx b/dashboard/components/export-csv/ExportCSV.tsx new file mode 100644 index 000000000..693c75afe --- /dev/null +++ b/dashboard/components/export-csv/ExportCSV.tsx @@ -0,0 +1,16 @@ +import { ToastProps } from '../toast/hooks/useToast'; +import ExportCSVButton from './ExportCSVButton'; +import useExportCSV from './useExportCSV'; + +type ExportCSVProps = { + id?: number; + setToast: (toast: ToastProps | undefined) => void; +}; + +function ExportCSV({ id, setToast }: ExportCSVProps) { + const { loading, exportCSV } = useExportCSV({ setToast }); + + return ; +} + +export default ExportCSV; diff --git a/dashboard/components/export-csv/ExportCSVButton.tsx b/dashboard/components/export-csv/ExportCSVButton.tsx new file mode 100644 index 000000000..979fa9597 --- /dev/null +++ b/dashboard/components/export-csv/ExportCSVButton.tsx @@ -0,0 +1,27 @@ +import Button from '../button/Button'; +import DownloadIcon from '../icons/DownloadIcon'; + +type ExportCSVButtonProps = { + id?: number; + loading: boolean; + exportCSV: (id?: number) => void; +}; + +function ExportCSVButton({ id, loading, exportCSV }: ExportCSVButtonProps) { + return ( + + ); +} + +export default ExportCSVButton; diff --git a/dashboard/components/export-csv/useExportCSV.tsx b/dashboard/components/export-csv/useExportCSV.tsx new file mode 100644 index 000000000..db13bbb80 --- /dev/null +++ b/dashboard/components/export-csv/useExportCSV.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import settingsService from '../../services/settingsService'; +import { ToastProps } from '../toast/hooks/useToast'; + +type useExportCSVProps = { + setToast: (toast: ToastProps | undefined) => void; +}; + +function useExportCSV({ setToast }: useExportCSVProps) { + const [loading, setLoading] = useState(false); + + function exportCSV(id?: number) { + if (!loading) { + setLoading(true); + } + + settingsService.exportCSV(id || undefined).then(res => { + if (res === Error) { + console.log(res); + setLoading(false); + setToast({ + hasError: true, + title: 'CSV not exported', + message: + 'There was an error exporting the CSV for this list of resources.' + }); + } else { + console.log(res); + setToast({ + hasError: false, + title: 'CSV exported', + message: 'The download of the CSV file should begin shortly.' + }); + setLoading(false); + } + }); + } + + return { loading, exportCSV }; +} + +export default useExportCSV; diff --git a/dashboard/components/inventory/components/view/InventoryViewHeader.tsx b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx index 2d28c00a8..a3654ddf1 100644 --- a/dashboard/components/inventory/components/view/InventoryViewHeader.tsx +++ b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx @@ -1,6 +1,7 @@ import { NextRouter } from 'next/router'; import { FormEvent, useState } from 'react'; import Button from '../../../button/Button'; +import ExportCSV from '../../../export-csv/ExportCSV'; import AlertIcon from '../../../icons/AlertIcon'; import ChevronDownIcon from '../../../icons/ChevronDownIcon'; import DeleteIcon from '../../../icons/DeleteIcon'; @@ -74,153 +75,143 @@ function InventoryViewHeader({ return (
{currentView && ( -
- {currentView.name} - -
- )} - - {dropdownIsOpen && ( <> -
-
-
- - - - - - - -
+
+ {currentView.name} +
- - )} - -
-
-
- -
-
-

- Are you sure you want to delete this view? -

-

- This is a permanent action. -

+ {dropdownIsOpen && ( + <> +
+
+
+ + + + + + + +
+
+ + )} + + +
+
+
+ +
+
+

+ Are you sure you want to delete this view? +

+

+ This is a permanent action. +

+
+
+
+ + +
-
-
- - -
-
- + + + )}
); } diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index cdd6703be..5c384be1f 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -346,6 +346,19 @@ const settingsService = { } catch (error) { return Error; } + }, + + async exportCSV(id?: number) { + try { + const res = await fetch( + `${BASE_URL}/resources/export-csv${id ? `?viewId=${id}` : ''}`, + settings('GET') + ); + const data = await res.json(); + return data; + } catch (error) { + return Error; + } } }; From 2326c3cdff80362de8097af9462aa60ed55bd68c Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Tue, 14 Mar 2023 16:18:51 +0100 Subject: [PATCH 03/12] feat: writing unit tests --- .../components/export-csv/ExportCSV.test.tsx | 14 +++++ dashboard/components/export-csv/ExportCSV.tsx | 2 +- .../components/export-csv/ExportCSVButton.tsx | 4 +- .../components/export-csv/useExportCSV.tsx | 6 +- dashboard/components/icons/CloseIcon.tsx | 20 +++++++ dashboard/components/icons/SearchIcon.tsx | 20 +++++++ .../components/InventorySearchBar.tsx | 58 ++++++++----------- .../inventory/components/InventoryTable.tsx | 13 ++++- dashboard/pages/inventory.tsx | 1 + dashboard/services/settingsService.ts | 2 +- 10 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 dashboard/components/export-csv/ExportCSV.test.tsx create mode 100644 dashboard/components/icons/CloseIcon.tsx create mode 100644 dashboard/components/icons/SearchIcon.tsx diff --git a/dashboard/components/export-csv/ExportCSV.test.tsx b/dashboard/components/export-csv/ExportCSV.test.tsx new file mode 100644 index 000000000..dcde39d74 --- /dev/null +++ b/dashboard/components/export-csv/ExportCSV.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@testing-library/react'; +import ExportCSVButton from './ExportCSVButton'; + +const props = { + id: undefined, + exportCSV: jest.fn(), + loading: false +}; + +describe('ExportCSV component', () => { + it('should render without crashing', () => { + render(); + }); +}); diff --git a/dashboard/components/export-csv/ExportCSV.tsx b/dashboard/components/export-csv/ExportCSV.tsx index 693c75afe..ac852fda7 100644 --- a/dashboard/components/export-csv/ExportCSV.tsx +++ b/dashboard/components/export-csv/ExportCSV.tsx @@ -3,7 +3,7 @@ import ExportCSVButton from './ExportCSVButton'; import useExportCSV from './useExportCSV'; type ExportCSVProps = { - id?: number; + id?: string; setToast: (toast: ToastProps | undefined) => void; }; diff --git a/dashboard/components/export-csv/ExportCSVButton.tsx b/dashboard/components/export-csv/ExportCSVButton.tsx index 979fa9597..504762579 100644 --- a/dashboard/components/export-csv/ExportCSVButton.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.tsx @@ -2,9 +2,9 @@ import Button from '../button/Button'; import DownloadIcon from '../icons/DownloadIcon'; type ExportCSVButtonProps = { - id?: number; + id?: string; loading: boolean; - exportCSV: (id?: number) => void; + exportCSV: (id?: string) => void; }; function ExportCSVButton({ id, loading, exportCSV }: ExportCSVButtonProps) { diff --git a/dashboard/components/export-csv/useExportCSV.tsx b/dashboard/components/export-csv/useExportCSV.tsx index db13bbb80..ffa0e3306 100644 --- a/dashboard/components/export-csv/useExportCSV.tsx +++ b/dashboard/components/export-csv/useExportCSV.tsx @@ -9,10 +9,8 @@ type useExportCSVProps = { function useExportCSV({ setToast }: useExportCSVProps) { const [loading, setLoading] = useState(false); - function exportCSV(id?: number) { - if (!loading) { - setLoading(true); - } + function exportCSV(id?: string) { + setLoading(true); settingsService.exportCSV(id || undefined).then(res => { if (res === Error) { diff --git a/dashboard/components/icons/CloseIcon.tsx b/dashboard/components/icons/CloseIcon.tsx new file mode 100644 index 000000000..8d1f737e4 --- /dev/null +++ b/dashboard/components/icons/CloseIcon.tsx @@ -0,0 +1,20 @@ +import { SVGProps } from 'react'; + +const CloseIcon = (props: SVGProps) => ( + + + +); + +export default CloseIcon; diff --git a/dashboard/components/icons/SearchIcon.tsx b/dashboard/components/icons/SearchIcon.tsx new file mode 100644 index 000000000..c421f82ff --- /dev/null +++ b/dashboard/components/icons/SearchIcon.tsx @@ -0,0 +1,20 @@ +import { SVGProps } from 'react'; + +const SearchIcon = (props: SVGProps) => ( + + + +); + +export default SearchIcon; diff --git a/dashboard/components/inventory/components/InventorySearchBar.tsx b/dashboard/components/inventory/components/InventorySearchBar.tsx index 4793e1ccb..2c9cd331c 100644 --- a/dashboard/components/inventory/components/InventorySearchBar.tsx +++ b/dashboard/components/inventory/components/InventorySearchBar.tsx @@ -1,53 +1,39 @@ +import { NextRouter } from 'next/router'; +import ExportCSV from '../../export-csv/ExportCSV'; +import CloseIcon from '../../icons/CloseIcon'; +import SearchIcon from '../../icons/SearchIcon'; +import { ToastProps } from '../../toast/hooks/useToast'; + type InventorySearchBarProps = { query: string; setQuery: (query: string) => void; error: boolean; + setToast: (toast: ToastProps | undefined) => void; + router: NextRouter; }; function InventorySearchBar({ query, setQuery, - error + error, + setToast, + router }: InventorySearchBarProps) { return ( <> {!error && (
{!query ? ( - - - +
+ +
) : ( - setQuery('')} - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - fill="none" - viewBox="0 0 24 24" - className="absolute top-[1.175rem] left-6 cursor-pointer" > - - + +
)} setQuery(e.target.value)} type="text" placeholder="Search by tags, service, name, region..." - className="w-full border-b border-black-200/30 bg-white py-4 pl-14 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none" + className="w-full border-b border-black-200/30 bg-white py-6 pl-14 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none" /> +
+ +
)} diff --git a/dashboard/components/inventory/components/InventoryTable.tsx b/dashboard/components/inventory/components/InventoryTable.tsx index a413903f7..bba267610 100644 --- a/dashboard/components/inventory/components/InventoryTable.tsx +++ b/dashboard/components/inventory/components/InventoryTable.tsx @@ -4,6 +4,7 @@ import formatNumber from '../../../utils/formatNumber'; import providers from '../../../utils/providerHelper'; import Checkbox from '../../checkbox/Checkbox'; import SkeletonInventory from '../../skeleton/SkeletonInventory'; +import { ToastProps } from '../../toast/hooks/useToast'; import { InventoryItem, InventoryStats @@ -31,6 +32,7 @@ type InventoryTableProps = { searchedLoading: boolean; hideResourceFromCustomView: () => void; hideResourcesLoading: boolean; + setToast: (toast: ToastProps | undefined) => void; }; function InventoryTable({ @@ -49,14 +51,21 @@ function InventoryTable({ router, searchedLoading, hideResourceFromCustomView, - hideResourcesLoading + hideResourcesLoading, + setToast }: InventoryTableProps) { return ( <> {((!error && inventory && inventory.length > 0) || (!error && searchedInventory)) && ( <> - +
{!error && ( diff --git a/dashboard/pages/inventory.tsx b/dashboard/pages/inventory.tsx index e9ac77069..e27a3e916 100644 --- a/dashboard/pages/inventory.tsx +++ b/dashboard/pages/inventory.tsx @@ -163,6 +163,7 @@ export default function Inventory() { searchedLoading={searchedLoading} hideResourceFromCustomView={hideResourceFromCustomView} hideResourcesLoading={hideResourcesLoading} + setToast={setToast} /> {/* Infite scroll trigger */} diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index 5c384be1f..be9aed1eb 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -348,7 +348,7 @@ const settingsService = { } }, - async exportCSV(id?: number) { + async exportCSV(id?: string) { try { const res = await fetch( `${BASE_URL}/resources/export-csv${id ? `?viewId=${id}` : ''}`, From a2919e6713b9e63f8f3b7c63ab2b045ebea09ac3 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Tue, 14 Mar 2023 16:20:18 +0100 Subject: [PATCH 04/12] chore: renaming file --- .../export-csv/{ExportCSV.test.tsx => ExportCSVButton.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dashboard/components/export-csv/{ExportCSV.test.tsx => ExportCSVButton.test.tsx} (100%) diff --git a/dashboard/components/export-csv/ExportCSV.test.tsx b/dashboard/components/export-csv/ExportCSVButton.test.tsx similarity index 100% rename from dashboard/components/export-csv/ExportCSV.test.tsx rename to dashboard/components/export-csv/ExportCSVButton.test.tsx From 730ad6d663799066ec7f81faad5321dbf7a60973 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Tue, 14 Mar 2023 16:37:31 +0100 Subject: [PATCH 05/12] feat: add to storybook --- dashboard/components/button/Button.tsx | 1 + .../export-csv/ExportCSVButton.stories.tsx | 19 +++++++++++++++++++ .../export-csv/ExportCSVButton.test.tsx | 8 +++++++- .../components/export-csv/useExportCSV.tsx | 2 -- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 dashboard/components/export-csv/ExportCSVButton.stories.tsx diff --git a/dashboard/components/button/Button.tsx b/dashboard/components/button/Button.tsx index 87ac2bcb9..3db909cf5 100644 --- a/dashboard/components/button/Button.tsx +++ b/dashboard/components/button/Button.tsx @@ -102,6 +102,7 @@ function Button({ xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" + data-testid="loading-spinner" > = { + title: 'Komiser/Export CSV', + component: ExportCSVButton, + tags: ['autodocs'], + argTypes: {} +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + loading: false, + exportCSV: () => {} + } +}; diff --git a/dashboard/components/export-csv/ExportCSVButton.test.tsx b/dashboard/components/export-csv/ExportCSVButton.test.tsx index dcde39d74..2edca2f7b 100644 --- a/dashboard/components/export-csv/ExportCSVButton.test.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.test.tsx @@ -7,8 +7,14 @@ const props = { loading: false }; -describe('ExportCSV component', () => { +describe('Export CSV component', () => { it('should render without crashing', () => { render(); }); + + it('should display loading spinner if loading is true', () => { + render(); + const loadingSpinner = screen.getByTestId('loading-spinner'); + expect(loadingSpinner).toBeInTheDocument(); + }); }); diff --git a/dashboard/components/export-csv/useExportCSV.tsx b/dashboard/components/export-csv/useExportCSV.tsx index ffa0e3306..6ae36ec54 100644 --- a/dashboard/components/export-csv/useExportCSV.tsx +++ b/dashboard/components/export-csv/useExportCSV.tsx @@ -14,7 +14,6 @@ function useExportCSV({ setToast }: useExportCSVProps) { settingsService.exportCSV(id || undefined).then(res => { if (res === Error) { - console.log(res); setLoading(false); setToast({ hasError: true, @@ -23,7 +22,6 @@ function useExportCSV({ setToast }: useExportCSVProps) { 'There was an error exporting the CSV for this list of resources.' }); } else { - console.log(res); setToast({ hasError: false, title: 'CSV exported', From 1638ade42441dea659d07c095a4e89fec3d71135 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Wed, 15 Mar 2023 13:01:50 +0100 Subject: [PATCH 06/12] chore: change exportCSV url --- dashboard/services/settingsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index be9aed1eb..01079e88c 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -351,7 +351,7 @@ const settingsService = { async exportCSV(id?: string) { try { const res = await fetch( - `${BASE_URL}/resources/export-csv${id ? `?viewId=${id}` : ''}`, + `${BASE_URL}/resources/export-csv${id ? `/${id}` : ''}`, settings('GET') ); const data = await res.json(); From 055379fd4af3fe67239528efc05586021e662e9f Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Wed, 15 Mar 2023 15:26:55 +0100 Subject: [PATCH 07/12] feat: hide download CSV button on filtered resources --- .../components/InventorySearchBar.tsx | 19 +++++++++++++------ dashboard/services/settingsService.ts | 7 ++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/dashboard/components/inventory/components/InventorySearchBar.tsx b/dashboard/components/inventory/components/InventorySearchBar.tsx index 2c9cd331c..7d820eb8b 100644 --- a/dashboard/components/inventory/components/InventorySearchBar.tsx +++ b/dashboard/components/inventory/components/InventorySearchBar.tsx @@ -42,13 +42,20 @@ function InventorySearchBar({ type="text" placeholder="Search by tags, service, name, region..." className="w-full border-b border-black-200/30 bg-white py-6 pl-14 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none" + autoComplete="off" + data-lpignore="true" + data-form-type="other" /> -
- -
+ {(router.asPath === '/inventory' || router.query.view) && ( +
+ +
+ )} )} diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index 01079e88c..3816c2dd3 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -350,12 +350,9 @@ const settingsService = { async exportCSV(id?: string) { try { - const res = await fetch( - `${BASE_URL}/resources/export-csv${id ? `/${id}` : ''}`, - settings('GET') + return window.location.replace( + `${BASE_URL}/resources/export-csv${id ? `/${id}` : ''}` ); - const data = await res.json(); - return data; } catch (error) { return Error; } From 26e947c98df3f579429d22a7cf7d38f250783cfb Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Thu, 16 Mar 2023 14:38:07 +0100 Subject: [PATCH 08/12] feat: add disabled state for filtered list --- dashboard/components/button/Button.tsx | 2 +- dashboard/components/export-csv/ExportCSV.tsx | 16 +++++-- .../export-csv/ExportCSVButton.test.tsx | 6 ++- .../components/export-csv/ExportCSVButton.tsx | 46 +++++++++++++------ .../components/export-csv/useExportCSV.tsx | 8 +++- .../components/InventorySearchBar.tsx | 22 +++------ .../inventory/components/InventoryTable.tsx | 1 - dashboard/components/tooltip/Tooltip.tsx | 18 +++++++- 8 files changed, 79 insertions(+), 40 deletions(-) diff --git a/dashboard/components/button/Button.tsx b/dashboard/components/button/Button.tsx index 3db909cf5..b30e2d612 100644 --- a/dashboard/components/button/Button.tsx +++ b/dashboard/components/button/Button.tsx @@ -59,7 +59,7 @@ function Button({ const primary = `${base} bg-gradient-to-br from-primary bg-secondary hover:bg-primary active:from-secondary active:bg-secondary text-white disabled:from-primary disabled:bg-secondary disabled:opacity-50`; - const secondary = `${base} hover:bg-black-100 active:bg-black-150 text-black-400 disabled:bg-black-100 disabled:opacity-50`; + const secondary = `${base} hover:bg-black-100 text-black-900 active:bg-black-150 text-black-400 disabled:hover:bg-transparent disabled:opacity-30`; const bulk = `${base} bg-white hover:bg-komiser-200 active:bg-komiser-300 text-secondary disabled:bg-white disabled:opacity-50`; diff --git a/dashboard/components/export-csv/ExportCSV.tsx b/dashboard/components/export-csv/ExportCSV.tsx index ac852fda7..0020cc490 100644 --- a/dashboard/components/export-csv/ExportCSV.tsx +++ b/dashboard/components/export-csv/ExportCSV.tsx @@ -3,14 +3,22 @@ import ExportCSVButton from './ExportCSVButton'; import useExportCSV from './useExportCSV'; type ExportCSVProps = { - id?: string; + displayInTable: boolean; setToast: (toast: ToastProps | undefined) => void; }; -function ExportCSV({ id, setToast }: ExportCSVProps) { - const { loading, exportCSV } = useExportCSV({ setToast }); +function ExportCSV({ displayInTable = false, setToast }: ExportCSVProps) { + const { id, isFilteredList, loading, exportCSV } = useExportCSV({ setToast }); - return ; + return ( + + ); } export default ExportCSV; diff --git a/dashboard/components/export-csv/ExportCSVButton.test.tsx b/dashboard/components/export-csv/ExportCSVButton.test.tsx index 2edca2f7b..68c7981c0 100644 --- a/dashboard/components/export-csv/ExportCSVButton.test.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.test.tsx @@ -3,8 +3,10 @@ import ExportCSVButton from './ExportCSVButton'; const props = { id: undefined, - exportCSV: jest.fn(), - loading: false + loading: false, + disabled: false, + displayInTable: false, + exportCSV: jest.fn() }; describe('Export CSV component', () => { diff --git a/dashboard/components/export-csv/ExportCSVButton.tsx b/dashboard/components/export-csv/ExportCSVButton.tsx index 504762579..a115f8429 100644 --- a/dashboard/components/export-csv/ExportCSVButton.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.tsx @@ -1,26 +1,46 @@ import Button from '../button/Button'; import DownloadIcon from '../icons/DownloadIcon'; +import Tooltip from '../tooltip/Tooltip'; type ExportCSVButtonProps = { id?: string; loading: boolean; + disabled: boolean; + displayInTable: boolean; exportCSV: (id?: string) => void; }; -function ExportCSVButton({ id, loading, exportCSV }: ExportCSVButtonProps) { +function ExportCSVButton({ + id, + loading, + disabled, + displayInTable, + exportCSV +}: ExportCSVButtonProps) { return ( - + <> +
+ +
+ {disabled && ( + + This feature isn't available yet. To download data from a + filtered table, save it as a view and download it from there. + + )} + ); } diff --git a/dashboard/components/export-csv/useExportCSV.tsx b/dashboard/components/export-csv/useExportCSV.tsx index 6ae36ec54..8dd39fe10 100644 --- a/dashboard/components/export-csv/useExportCSV.tsx +++ b/dashboard/components/export-csv/useExportCSV.tsx @@ -1,3 +1,4 @@ +import { useRouter } from 'next/router'; import { useState } from 'react'; import settingsService from '../../services/settingsService'; import { ToastProps } from '../toast/hooks/useToast'; @@ -8,6 +9,7 @@ type useExportCSVProps = { function useExportCSV({ setToast }: useExportCSVProps) { const [loading, setLoading] = useState(false); + const router = useRouter(); function exportCSV(id?: string) { setLoading(true); @@ -32,7 +34,11 @@ function useExportCSV({ setToast }: useExportCSVProps) { }); } - return { loading, exportCSV }; + const isFilteredList = + Object.keys(router.query).length > 0 && !router.query.view; + const id = router.query.view ? router.query.view.toString() : undefined; + + return { id, isFilteredList, loading, exportCSV }; } export default useExportCSV; diff --git a/dashboard/components/inventory/components/InventorySearchBar.tsx b/dashboard/components/inventory/components/InventorySearchBar.tsx index 7d820eb8b..0a5cc46ea 100644 --- a/dashboard/components/inventory/components/InventorySearchBar.tsx +++ b/dashboard/components/inventory/components/InventorySearchBar.tsx @@ -1,4 +1,3 @@ -import { NextRouter } from 'next/router'; import ExportCSV from '../../export-csv/ExportCSV'; import CloseIcon from '../../icons/CloseIcon'; import SearchIcon from '../../icons/SearchIcon'; @@ -9,20 +8,18 @@ type InventorySearchBarProps = { setQuery: (query: string) => void; error: boolean; setToast: (toast: ToastProps | undefined) => void; - router: NextRouter; }; function InventorySearchBar({ query, setQuery, error, - setToast, - router + setToast }: InventorySearchBarProps) { return ( <> {!error && ( -
+
{!query ? (
@@ -41,21 +38,14 @@ function InventorySearchBar({ onChange={e => setQuery(e.target.value)} type="text" placeholder="Search by tags, service, name, region..." - className="w-full border-b border-black-200/30 bg-white py-6 pl-14 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none" + className="w-full rounded-t-lg border-b border-black-200/30 bg-white py-6 pl-14 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none" autoComplete="off" data-lpignore="true" data-form-type="other" /> - {(router.asPath === '/inventory' || router.query.view) && ( -
- -
- )} +
+ +
)} diff --git a/dashboard/components/inventory/components/InventoryTable.tsx b/dashboard/components/inventory/components/InventoryTable.tsx index bba267610..bca698f29 100644 --- a/dashboard/components/inventory/components/InventoryTable.tsx +++ b/dashboard/components/inventory/components/InventoryTable.tsx @@ -64,7 +64,6 @@ function InventoryTable({ setQuery={setQuery} error={error} setToast={setToast} - router={router} />
diff --git a/dashboard/components/tooltip/Tooltip.tsx b/dashboard/components/tooltip/Tooltip.tsx index 21b1242fc..8593cc89f 100644 --- a/dashboard/components/tooltip/Tooltip.tsx +++ b/dashboard/components/tooltip/Tooltip.tsx @@ -1,14 +1,28 @@ +import classNames from 'classnames'; import { ReactNode } from 'react'; type TooltipProps = { children: ReactNode; + top?: 'sm' | 'md' | 'lg'; + align?: 'left' | 'center' | 'right'; + width?: 'sm' | 'md' | 'lg'; }; -function Tooltip({ children }: TooltipProps) { +function Tooltip({ + children, + top = 'md', + align = 'left', + width = 'md' +}: TooltipProps) { return ( From 64da826bc3e7f798c84da0a5afb7364b38862e1f Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Thu, 16 Mar 2023 14:43:51 +0100 Subject: [PATCH 09/12] feat: add test to expect tooltip on disabled state --- dashboard/components/export-csv/ExportCSVButton.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dashboard/components/export-csv/ExportCSVButton.test.tsx b/dashboard/components/export-csv/ExportCSVButton.test.tsx index 68c7981c0..ab914bc87 100644 --- a/dashboard/components/export-csv/ExportCSVButton.test.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.test.tsx @@ -19,4 +19,10 @@ describe('Export CSV component', () => { const loadingSpinner = screen.getByTestId('loading-spinner'); expect(loadingSpinner).toBeInTheDocument(); }); + + it('should display the auxiliary info in the tooltip if disabled is true', () => { + render(); + const tooltip = screen.getByRole('tooltip'); + expect(tooltip).toBeInTheDocument(); + }); }); From 8694d37c565399b17c86d7d571ec92bfc4912a05 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Thu, 16 Mar 2023 14:47:04 +0100 Subject: [PATCH 10/12] fix: lint --- dashboard/components/export-csv/ExportCSV.tsx | 2 +- .../inventory/components/view/InventoryViewHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/components/export-csv/ExportCSV.tsx b/dashboard/components/export-csv/ExportCSV.tsx index 0020cc490..7b2fa9598 100644 --- a/dashboard/components/export-csv/ExportCSV.tsx +++ b/dashboard/components/export-csv/ExportCSV.tsx @@ -3,7 +3,7 @@ import ExportCSVButton from './ExportCSVButton'; import useExportCSV from './useExportCSV'; type ExportCSVProps = { - displayInTable: boolean; + displayInTable?: boolean; setToast: (toast: ToastProps | undefined) => void; }; diff --git a/dashboard/components/inventory/components/view/InventoryViewHeader.tsx b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx index a3654ddf1..b6d7e6b2e 100644 --- a/dashboard/components/inventory/components/view/InventoryViewHeader.tsx +++ b/dashboard/components/inventory/components/view/InventoryViewHeader.tsx @@ -153,7 +153,7 @@ function InventoryViewHeader({ Copy view link - + diff --git a/dashboard/components/export-csv/useExportCSV.tsx b/dashboard/components/export-csv/useExportCSV.tsx deleted file mode 100644 index 8dd39fe10..000000000 --- a/dashboard/components/export-csv/useExportCSV.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useRouter } from 'next/router'; -import { useState } from 'react'; -import settingsService from '../../services/settingsService'; -import { ToastProps } from '../toast/hooks/useToast'; - -type useExportCSVProps = { - setToast: (toast: ToastProps | undefined) => void; -}; - -function useExportCSV({ setToast }: useExportCSVProps) { - const [loading, setLoading] = useState(false); - const router = useRouter(); - - function exportCSV(id?: string) { - setLoading(true); - - settingsService.exportCSV(id || undefined).then(res => { - if (res === Error) { - setLoading(false); - setToast({ - hasError: true, - title: 'CSV not exported', - message: - 'There was an error exporting the CSV for this list of resources.' - }); - } else { - setToast({ - hasError: false, - title: 'CSV exported', - message: 'The download of the CSV file should begin shortly.' - }); - setLoading(false); - } - }); - } - - const isFilteredList = - Object.keys(router.query).length > 0 && !router.query.view; - const id = router.query.view ? router.query.view.toString() : undefined; - - return { id, isFilteredList, loading, exportCSV }; -} - -export default useExportCSV; diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index 3816c2dd3..d25b5de02 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -348,14 +348,10 @@ const settingsService = { } }, - async exportCSV(id?: string) { - try { - return window.location.replace( - `${BASE_URL}/resources/export-csv${id ? `/${id}` : ''}` - ); - } catch (error) { - return Error; - } + exportCSV(id?: string) { + return window.location.replace( + `${BASE_URL}/resources/export-csv${id ? `/${id}` : ''}` + ); } }; From 279259f7e3e1afc50761d49aa33b5f2e5df62dc9 Mon Sep 17 00:00:00 2001 From: "Victor F. Santos" Date: Thu, 16 Mar 2023 16:29:52 +0100 Subject: [PATCH 12/12] fix: export CSV stories --- .../export-csv/ExportCSVButton.stories.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dashboard/components/export-csv/ExportCSVButton.stories.tsx b/dashboard/components/export-csv/ExportCSVButton.stories.tsx index ec5a21793..2ac770e55 100644 --- a/dashboard/components/export-csv/ExportCSVButton.stories.tsx +++ b/dashboard/components/export-csv/ExportCSVButton.stories.tsx @@ -5,7 +5,16 @@ const meta: Meta = { title: 'Komiser/Export CSV', component: ExportCSVButton, tags: ['autodocs'], - argTypes: {} + argTypes: {}, + decorators: [ + Story => ( +
+
+ +
+
+ ) + ] }; export default meta; @@ -13,7 +22,8 @@ type Story = StoryObj; export const Primary: Story = { args: { - loading: false, + disabled: false, + displayInTable: false, exportCSV: () => {} } };