From e086e440182928cbb128e7b18738d5ab71151ffa Mon Sep 17 00:00:00 2001 From: Gustav Eikaas <guei@equinor.com> Date: Mon, 20 Nov 2023 09:15:55 +0100 Subject: [PATCH 1/2] fix: garden group crashing --- packages/garden/src/lib/components/Garden.tsx | 20 +++--- .../GardenItemContainer.tsx | 4 +- .../GroupingSelector/GroupingSelector.tsx | 72 +++++++++---------- .../components/ViewSettings/ViewSettings.tsx | 7 -- .../VirtualContainer/VirtualContainer.tsx | 36 ++-------- .../defaultComponents/item/SubGroupItem.tsx | 4 +- .../garden/src/lib/context/gardenContext.tsx | 31 +++++++- .../src/lib/hooks/useExpandSubgroups.ts | 10 ++- packages/workspace-fusion/package.json | 2 +- 9 files changed, 93 insertions(+), 93 deletions(-) diff --git a/packages/garden/src/lib/components/Garden.tsx b/packages/garden/src/lib/components/Garden.tsx index 2df389ad9..3c78b8eb0 100644 --- a/packages/garden/src/lib/components/Garden.tsx +++ b/packages/garden/src/lib/components/Garden.tsx @@ -30,11 +30,15 @@ export type GardenMetaRequest = { dateVariant?: string | null; }; -export type GardenDataSource<TContext> = { - getGardenMeta: (request: GardenMetaRequest, context: TContext, signal?: AbortSignal) => Promise<GardenMeta>; - getBlockAsync: (args: GetBlockRequestArgs, context: TContext, signal?: AbortSignal) => Promise<GardenGroup<any>[]>; - getHeader: (args: GetHeaderBlockRequestArgs, context: TContext, signal?: AbortSignal) => Promise<GardenHeaderGroup[]>; - getSubgroupItems: (args: GetSubgroupItemsArgs, context: TContext, signal?: AbortSignal) => Promise<any[]>; +export type GardenDataSource<TContext = undefined> = { + getGardenMeta: (request: GardenMetaRequest, context?: TContext, signal?: AbortSignal) => Promise<GardenMeta>; + getBlockAsync: (args: GetBlockRequestArgs, context?: TContext, signal?: AbortSignal) => Promise<GardenGroup<any>[]>; + getHeader: ( + args: GetHeaderBlockRequestArgs, + context?: TContext, + signal?: AbortSignal + ) => Promise<GardenHeaderGroup[]>; + getSubgroupItems: (args: GetSubgroupItemsArgs, context?: TContext, signal?: AbortSignal) => Promise<any[]>; }; interface GardenProps<TData extends Record<PropertyKey, unknown>, TContext = undefined> { @@ -96,11 +100,13 @@ export function Garden<TData extends Record<PropertyKey, unknown>, TContext = un return ( <QueryClientProvider client={client.current}> - <ErrorBoundary FallbackComponent={GardenError}> + <ErrorBoundary FallbackComponent={() => <GardenError />}> <Suspense fallback={<SplashScreen />}> <GardenContextProvider getIdentifier={getIdentifier} timeInterval={timeInterval} + context={context} + dataSource={dataSource} dateVariant={dateVariant} initialGrouping={groupingKeys} selected={selected} @@ -121,11 +127,9 @@ export function Garden<TData extends Record<PropertyKey, unknown>, TContext = un // Hides ViewSettings sidebar when sidesheet is open selected ? null : ( <ViewSettings - dataSource={dataSource} dateVariant={dateVariant} groupingKeys={groupingKeys} timeInterval={timeInterval} - context={context} onChangeDateVariant={onChangeDateVariant} onChangeTimeInterval={onChangetimeInterval} setGroupingKeys={setGroupingKeys} diff --git a/packages/garden/src/lib/components/GardenItemContainer/GardenItemContainer.tsx b/packages/garden/src/lib/components/GardenItemContainer/GardenItemContainer.tsx index 13f246ead..ef2da6925 100644 --- a/packages/garden/src/lib/components/GardenItemContainer/GardenItemContainer.tsx +++ b/packages/garden/src/lib/components/GardenItemContainer/GardenItemContainer.tsx @@ -47,7 +47,7 @@ export const GardenItemContainer = <TData extends Record<PropertyKey, unknown>, props: PackageContainerProps<TData, TContext> ): JSX.Element => { const { - selectionService: { selection, selectNode }, + selectionService: { selection }, } = useGarden(); const { @@ -193,7 +193,7 @@ export const GardenItemContainer = <TData extends Record<PropertyKey, unknown>, const flatIndex = calculatedIndex; - if (flatIndex.isSubgroupItem) { + if (flatIndex.isSubgroupItem && groupingKeys.length > 1) { const query = queries[calculateActualIndex(expandedIndexes, flatIndex.parent.index).actualIndex]; return ( <SubGroupItem diff --git a/packages/garden/src/lib/components/GroupingSelector/GroupingSelector.tsx b/packages/garden/src/lib/components/GroupingSelector/GroupingSelector.tsx index 5c4d322b5..b9c78a6e9 100644 --- a/packages/garden/src/lib/components/GroupingSelector/GroupingSelector.tsx +++ b/packages/garden/src/lib/components/GroupingSelector/GroupingSelector.tsx @@ -1,8 +1,6 @@ -import { Autocomplete, Divider, EdsProvider, Label, Radio } from '@equinor/eds-core-react'; -import { useQuery } from '@tanstack/react-query'; -import { Fragment, useRef } from 'react'; +import { Autocomplete, CircularProgress, Divider, EdsProvider, Label, Radio } from '@equinor/eds-core-react'; +import { Fragment, startTransition, useRef } from 'react'; import { GroupingOption } from '../../types'; -import { GardenDataSource } from '../Garden'; import { RadioButtonWrapper, RadioCategoryWrapper, @@ -12,10 +10,9 @@ import { StyledGroupHeader, StyledSubGroupHeader, } from './groupingSelector.styles'; +import { useGarden } from '../../hooks/useGarden'; type GroupingSelectorProps<TContext> = { - dataSource: GardenDataSource<TContext>; - context: TContext; setGroupingKeys: (keys: string[]) => void; groupingKeys: string[]; timeInterval: string | null; @@ -25,8 +22,6 @@ type GroupingSelectorProps<TContext> = { }; export function GroupingSelector<TContext>({ - dataSource, - context, timeInterval, dateVariant, setGroupingKeys, @@ -34,35 +29,9 @@ export function GroupingSelector<TContext>({ onChangeDateVarient, groupingKeys, }: GroupingSelectorProps<TContext>): JSX.Element | null { - const { data } = useQuery(['garden', ...groupingKeys, timeInterval, dateVariant, context], { - refetchOnWindowFocus: false, - suspense: true, - useErrorBoundary: true, - keepPreviousData: false, - cacheTime: Infinity, - staleTime: Infinity, - queryFn: ({ signal }) => - dataSource.getGardenMeta({ groupingKeys, timeInterval, dateVariant }, context, signal ?? new AbortSignal()), - }); - const selectorRef = useRef(null); - const setGardenKey = (key: string) => { - const foundGroupingOption = data?.allGroupingOptions.find((option) => option.groupingKey === key); - if (!foundGroupingOption) { - throw new Error('Invalid grouping option'); - } - - if (!foundGroupingOption?.timeInterval?.includes(timeInterval ?? '')) { - onChangeTimeInterval(foundGroupingOption.timeInterval?.at(0) ?? null); - } - - if (!foundGroupingOption?.dateVariant?.includes(dateVariant ?? '')) { - onChangeDateVarient(foundGroupingOption.dateVariant?.at(0) ?? null); - } - - setGroupingKeys([key]); - }; + const { gardenMetaQuery } = useGarden(); const handleExistingSelectionChange = (newValue: string | null | undefined) => { const gardenKey = groupingKeys.at(0); @@ -70,7 +39,9 @@ export function GroupingSelector<TContext>({ throw new Error(''); } const newKeys = newValue == null ? [gardenKey] : [gardenKey, newValue]; - setGroupingKeys(newKeys); + startTransition(() => { + setGroupingKeys(newKeys); + }); }; const handleGardenKeyChange = (newValue: string | null | undefined) => { @@ -78,10 +49,31 @@ export function GroupingSelector<TContext>({ keyFromLabel && setGardenKey(keyFromLabel); }; - if (!data) { + if (gardenMetaQuery.isLoading) { + return <CircularProgress size={48} />; + } + + if (!gardenMetaQuery.data) { throw new Error('An error occurred while fetching grouping selections'); } + const setGardenKey = (key: string) => { + const foundGroupingOption = gardenMetaQuery.data.allGroupingOptions.find((option) => option.groupingKey === key); + if (!foundGroupingOption) { + throw new Error('Invalid grouping option'); + } + + if (!foundGroupingOption?.timeInterval?.includes(timeInterval ?? '')) { + onChangeTimeInterval(foundGroupingOption.timeInterval?.at(0) ?? null); + } + + if (!foundGroupingOption?.dateVariant?.includes(dateVariant ?? '')) { + onChangeDateVarient(foundGroupingOption.dateVariant?.at(0) ?? null); + } + + setGroupingKeys([key]); + }; + return ( <EdsProvider density="compact"> <SelectorBody> @@ -91,7 +83,7 @@ export function GroupingSelector<TContext>({ <Autocomplete ref={selectorRef} key={groupingKeys[0]} - options={data.allGroupingOptions.map((option: GroupingOption) => option.groupingKey)} + options={gardenMetaQuery.data.allGroupingOptions.map((option: GroupingOption) => option.groupingKey)} label={'Group by'} hideClearButton multiple={false} @@ -99,14 +91,14 @@ export function GroupingSelector<TContext>({ onOptionsChange={(changes) => handleGardenKeyChange(changes.selectedItems[0])} /> <Autocomplete - options={data.validGroupingOptions} + options={gardenMetaQuery.data.validGroupingOptions} label={'Then Group by'} selectedOptions={[groupingKeys.at(1)]} onOptionsChange={(changes) => handleExistingSelectionChange(changes.selectedItems[0])} /> </StyledAutoCompleteWrapper> - {data.allGroupingOptions.map((groupingOption) => { + {gardenMetaQuery.data.allGroupingOptions.map((groupingOption) => { // Check if dateVariant or timeInterval is defined const hasDateVariant = !!groupingOption.dateVariant; const hasTimeInterval = !!groupingOption.timeInterval; diff --git a/packages/garden/src/lib/components/ViewSettings/ViewSettings.tsx b/packages/garden/src/lib/components/ViewSettings/ViewSettings.tsx index a5c70d618..9d8f5eb46 100644 --- a/packages/garden/src/lib/components/ViewSettings/ViewSettings.tsx +++ b/packages/garden/src/lib/components/ViewSettings/ViewSettings.tsx @@ -1,15 +1,12 @@ import { Button, Icon } from '@equinor/eds-core-react'; import { expand, collapse } from '@equinor/eds-icons'; import { useState } from 'react'; -import { GardenDataSource } from '../Garden'; import { GroupingSelector } from '../GroupingSelector/GroupingSelector'; import { StyledViewSettings } from './viewSettings.styles'; const LOCAL_STORAGE_KEY = 'WorkspaceSidebarToggleState'; interface ViewSettingsProps<TData extends Record<PropertyKey, unknown>, TContext = undefined> { - dataSource: GardenDataSource<TContext>; - context?: TContext; groupingKeys: string[]; timeInterval: string | null; dateVariant: string | null; @@ -21,8 +18,6 @@ interface ViewSettingsProps<TData extends Record<PropertyKey, unknown>, TContext Icon.add({ expand, collapse }); export function ViewSettings<TData extends Record<PropertyKey, unknown>, TContext = undefined>({ - dataSource, - context, groupingKeys, timeInterval, dateVariant, @@ -57,8 +52,6 @@ export function ViewSettings<TData extends Record<PropertyKey, unknown>, TContex onChangeTimeInterval={onChangeTimeInterval} dateVariant={dateVariant} onChangeDateVarient={onChangeDateVariant} - context={context as any} - dataSource={dataSource} /> </> ) : ( diff --git a/packages/garden/src/lib/components/VirtualContainer/VirtualContainer.tsx b/packages/garden/src/lib/components/VirtualContainer/VirtualContainer.tsx index a3f97a51e..2df885df2 100644 --- a/packages/garden/src/lib/components/VirtualContainer/VirtualContainer.tsx +++ b/packages/garden/src/lib/components/VirtualContainer/VirtualContainer.tsx @@ -1,4 +1,3 @@ -import { useQuery } from '@tanstack/react-query'; import { useItemWidths } from '../../hooks'; import { useGarden } from '../../hooks/useGarden'; import { useGardenConfig } from '../../hooks/useGardenConfig'; @@ -9,6 +8,7 @@ import { StyledVirtualContainer } from './virtualContainer.styles'; import { info_circle } from '@equinor/eds-icons'; import { Icon } from '@equinor/eds-core-react'; import styled from 'styled-components'; +import { SplashScreen } from '../splashScreen/SplashScreen'; Icon.add({ info_circle }); type VirtualContainerProps<TContext = undefined> = { @@ -21,33 +21,17 @@ export const VirtualContainer = <TContext,>({ context, }: VirtualContainerProps<TContext>): JSX.Element | null => { const { onClickItem } = useGardenConfig(); - const { - groupingService: { groupingKeys, timeInterval, dateVariant }, - } = useGarden(); + const { gardenMetaQuery } = useGarden(); - const { data, isFetching } = useQuery(['garden', ...groupingKeys, timeInterval, dateVariant, context], { - refetchOnWindowFocus: false, - suspense: true, - useErrorBoundary: true, - keepPreviousData: false, - cacheTime: Infinity, - staleTime: Infinity, - queryFn: ({ signal }) => - dataSource.getGardenMeta( - { - timeInterval, - dateVariant, - groupingKeys, - }, - context, - signal ?? new AbortSignal() - ), - }); + if (gardenMetaQuery.isLoading) { + return <SplashScreen />; + } - if (!data) { + if (!gardenMetaQuery.data) { // Will never happen when suspense is true throw new Error(); } + const { data } = gardenMetaQuery; const amountOfColumns = data.columnCount; const columnWidth = data.columnWidth || 300; @@ -73,14 +57,8 @@ export const VirtualContainer = <TContext,>({ return null; } - //TODO: temp fix, should show skeletons - if (isFetching) { - return null; - } - return ( <> - {/* <ReactQueryDevtools /> */} <StyledVirtualContainer id={'garden_root'}> <ExpandProvider initialWidths={widths} defaultColumnWidth={columnWidth}> <VirtualGarden diff --git a/packages/garden/src/lib/components/defaultComponents/item/SubGroupItem.tsx b/packages/garden/src/lib/components/defaultComponents/item/SubGroupItem.tsx index 1bdae054e..75dbceb1b 100644 --- a/packages/garden/src/lib/components/defaultComponents/item/SubGroupItem.tsx +++ b/packages/garden/src/lib/components/defaultComponents/item/SubGroupItem.tsx @@ -36,8 +36,6 @@ export const SubGroupItem = ({ itemIndex, parentRef, }: SubGroupItemProps) => { - const { isLoading, error, data, refetch } = query; - const { selectionService: { selection }, } = useGarden(); @@ -47,6 +45,8 @@ export const SubGroupItem = ({ getDisplayName, } = useGardenConfig(); + const { isLoading, error, data, refetch } = query; + if (isLoading) { /** Skeleton loading state */ return ( diff --git a/packages/garden/src/lib/context/gardenContext.tsx b/packages/garden/src/lib/context/gardenContext.tsx index 3441ccf62..d8eca1ff7 100644 --- a/packages/garden/src/lib/context/gardenContext.tsx +++ b/packages/garden/src/lib/context/gardenContext.tsx @@ -1,9 +1,12 @@ import { PropsWithChildren, createContext, useCallback, useEffect, useState } from 'react'; -import { GetIdentifier } from '../types'; +import { GardenMeta, GetIdentifier } from '../types'; +import { UseQueryResult, useQuery } from '@tanstack/react-query'; +import { GardenDataSource } from '../components'; type GardenState = { selectionService: SelectionService; groupingService: GroupingService; + gardenMetaQuery: UseQueryResult<GardenMeta, unknown>; }; type GroupingService = { @@ -20,20 +23,42 @@ type SelectionService = { export const GardenContext = createContext<GardenState | null>(null); -export const GardenContextProvider = <T,>( +export const GardenContextProvider = <T, TContext = undefined>( props: PropsWithChildren<{ getIdentifier: GetIdentifier<T>; selected: string | null; initialGrouping: string[]; timeInterval: string | null; dateVariant: string | null; + dataSource: GardenDataSource<TContext>; + context: TContext | undefined; }> ) => { + const gardenMetaQuery = useQuery( + ['garden', ...props.initialGrouping, props.timeInterval, props.dateVariant, props.context], + { + refetchOnWindowFocus: false, + suspense: true, + useErrorBoundary: true, + keepPreviousData: false, + cacheTime: Infinity, + staleTime: Infinity, + queryFn: ({ signal }) => + props.dataSource.getGardenMeta( + { groupingKeys: props.initialGrouping, timeInterval: props.timeInterval, dateVariant: props.dateVariant }, + props.context, + signal ?? new AbortSignal() + ), + } + ); + const selectionService = useSelectionService(props.getIdentifier, props.selected); const groupingService = useGroupingService(props.initialGrouping, props.timeInterval, props.dateVariant); return ( - <GardenContext.Provider value={{ groupingService, selectionService }}>{props.children}</GardenContext.Provider> + <GardenContext.Provider value={{ groupingService, selectionService, gardenMetaQuery }}> + {props.children} + </GardenContext.Provider> ); }; diff --git a/packages/garden/src/lib/hooks/useExpandSubgroups.ts b/packages/garden/src/lib/hooks/useExpandSubgroups.ts index d93afb1b2..1120af2f6 100644 --- a/packages/garden/src/lib/hooks/useExpandSubgroups.ts +++ b/packages/garden/src/lib/hooks/useExpandSubgroups.ts @@ -1,8 +1,16 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { ExpandedWithRange, Expanded } from '../components/VirtualGarden'; +import { useGarden } from './useGarden'; export const useExpandedSubGroups = (columnCount: number) => { + const { + groupingService: { groupingKeys, dateVariant, timeInterval }, + } = useGarden(); + const [expanded, setExpanded] = useState<ExpandedWithRange[][]>(new Array(columnCount).fill(0).map(() => [])); + useEffect(() => { + setExpanded(new Array(columnCount).fill(0).map(() => [])); + }, [groupingKeys.toString(), dateVariant, timeInterval, columnCount]); const expandColumn = (columnIndex: number, item: Expanded) => { const expandedIndexes = expanded[columnIndex]; diff --git a/packages/workspace-fusion/package.json b/packages/workspace-fusion/package.json index b383745e8..ab743bce9 100644 --- a/packages/workspace-fusion/package.json +++ b/packages/workspace-fusion/package.json @@ -1,6 +1,6 @@ { "name": "@equinor/workspace-fusion", - "version": "6.0.1", + "version": "6.0.2", "type": "module", "sideEffects": false, "license": "MIT", From eca642d2205546c369e5b672831f9bf572fba3ef Mon Sep 17 00:00:00 2001 From: Gustav Eikaas <guei@equinor.com> Date: Mon, 20 Nov 2023 09:18:38 +0100 Subject: [PATCH 2/2] chore: release --- packages/garden/package.json | 2 +- packages/workspace-fusion/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/garden/package.json b/packages/garden/package.json index 8a2e54b83..c1ea514e0 100644 --- a/packages/garden/package.json +++ b/packages/garden/package.json @@ -1,6 +1,6 @@ { "name": "@equinor/workspace-garden", - "version": "6.0.0", + "version": "6.0.1", "type": "module", "sideEffects": false, "license": "MIT", diff --git a/packages/workspace-fusion/package.json b/packages/workspace-fusion/package.json index ab743bce9..b383745e8 100644 --- a/packages/workspace-fusion/package.json +++ b/packages/workspace-fusion/package.json @@ -1,6 +1,6 @@ { "name": "@equinor/workspace-fusion", - "version": "6.0.2", + "version": "6.0.1", "type": "module", "sideEffects": false, "license": "MIT",