From e3b4959e924abb81948cf95e986f6b523d245433 Mon Sep 17 00:00:00 2001
From: Gustav Eikaas <guei@equinor.com>
Date: Mon, 20 Nov 2023 11:56:55 +0100
Subject: [PATCH 1/3] feat: powerbi improvements

---
 .../src/lib/components/Filter/Filter.tsx      | 64 +++++++++++++++++--
 .../expandedFilter/ExpandedFilter.tsx         |  9 +--
 .../src/lib/components/skeleton/Skeleton.tsx  | 30 +++++++++
 .../ToggleHideFilterPopover.tsx               | 18 ++++--
 .../common/components/TabNavigation.tsx       |  8 +--
 5 files changed, 111 insertions(+), 18 deletions(-)
 create mode 100644 packages/power-bi/src/lib/components/skeleton/Skeleton.tsx

diff --git a/packages/power-bi/src/lib/components/Filter/Filter.tsx b/packages/power-bi/src/lib/components/Filter/Filter.tsx
index 4afb744cb..173a55190 100644
--- a/packages/power-bi/src/lib/components/Filter/Filter.tsx
+++ b/packages/power-bi/src/lib/components/Filter/Filter.tsx
@@ -8,6 +8,8 @@ import { getActiveFilterValues } from '../../utils/getActiveFilterValues';
 import { getFiltersAsync } from '../../utils/getFiltersAsync';
 import { PowerBIQuickFilter } from '../QuickFilter/QuickFilter';
 import { search, playlist_add, drag_handle } from '@equinor/eds-icons';
+import { tokens } from '@equinor/eds-tokens';
+import { Skeleton } from '../skeleton/Skeleton';
 
 Icon.add({ search, playlist_add, drag_handle });
 
@@ -39,10 +41,22 @@ type PowerBIFilterProps = {
   options?: PowerBIFilterOptions;
 };
 
+const getVisibleFiltersFromLocalStorage = (reportId: string) => {
+  const value = localStorage.getItem(`${reportId}-filters`);
+  if (!value) return null;
+  const parsedValue = JSON.parse(value);
+  if (Array.isArray(parsedValue)) {
+    return parsedValue as string[];
+  }
+  return null;
+};
+
 export const PowerBIFilter = ({ report, options }: PowerBIFilterProps): JSX.Element | null => {
   const [activeFilters, setActiveFilters] = useState<Record<string, ActiveFilter[]>>({});
   const [slicerFilters, setSlicerFilters] = useState<PowerBiFilter[] | null>(null);
-  const [filterGroupVisible, setFilterGroupVisible] = useState<string[]>(options?.defaultFilterGroupVisible || []);
+  const [filterGroupVisible, setFilterGroupVisible] = useState<string[]>(
+    getVisibleFiltersFromLocalStorage(report.getId()) ?? options?.defaultFilterGroupVisible ?? []
+  );
   const [isFilterExpanded, setIsFilterExpanded] = useState(false);
 
   const handleChangeGroup = async (filter: PowerBiFilter) => {
@@ -161,7 +175,10 @@ export const PowerBIFilter = ({ report, options }: PowerBIFilterProps): JSX.Elem
         const defaultActiveFilters = await getActiveFilterValues(filters);
         setSlicerFilters(filters.sort((a, b) => a.type.localeCompare(b.type)));
         setActiveFilters(defaultActiveFilters);
-        if (!options?.defaultFilterGroupVisible) {
+        const state = getVisibleFiltersFromLocalStorage(report.getId());
+        if (state) {
+          setFilterGroupVisible(state);
+        } else {
           setFilterGroupVisible(filters.map((s) => s.type));
         }
       };
@@ -183,13 +200,21 @@ export const PowerBIFilter = ({ report, options }: PowerBIFilterProps): JSX.Elem
         setSlicerFilters(filters.sort((a, b) => a.type.localeCompare(b.type)));
 
         const filterGroupNames = getActiveFilterGroupArray(activeFilters);
-        setFilterGroupVisible((s) => [...s, ...filterGroupNames].filter((v, i, a) => a.indexOf(v) === i));
+
+        const reportId = report.getId();
+
+        const state = getVisibleFiltersFromLocalStorage(reportId);
+        if (state) {
+          setFilterGroupVisible(state);
+        } else {
+          setFilterGroupVisible((s) => [...s, ...filterGroupNames].filter((v, i, a) => a.indexOf(v) === i));
+        }
       };
       reCreateFilters();
     }
   }, [activeFilters, Object.keys(activeFilters).length]);
 
-  if (!slicerFilters || Object.keys(activeFilters).length === 0) return null;
+  if (!slicerFilters || Object.keys(activeFilters).length === 0) return <QuickFilterLoading />;
 
   const controller: FilterController = {
     handleChangeGroup,
@@ -203,8 +228,37 @@ export const PowerBIFilter = ({ report, options }: PowerBIFilterProps): JSX.Elem
     slicerFilters,
     activeFilters,
     visibleFilters: filterGroupVisible,
-    setVisibleFilters: setFilterGroupVisible,
+    setVisibleFilters: (e) => {
+      const reportId = report.getId();
+      if (reportId) {
+        localStorage.setItem(`${reportId}-filters`, JSON.stringify(e));
+      }
+      setFilterGroupVisible(e);
+    },
   };
 
   return <PowerBIQuickFilter controller={controller} />;
 };
+
+function QuickFilterLoading() {
+  return (
+    <div
+      style={{
+        height: '48px',
+        width: '100%',
+        display: 'flex',
+        background: tokens.colors.ui.background__light.hex,
+        alignItems: 'center',
+        gap: '1em',
+        paddingLeft: '1em',
+      }}
+    >
+      <Skeleton height={24} width={160} />
+      <Skeleton height={24} width={160} />
+      <Skeleton height={24} width={160} />
+      <Skeleton height={24} width={160} />
+      <Skeleton height={24} width={160} />
+      <Skeleton height={24} width={160} />
+    </div>
+  );
+}
diff --git a/packages/power-bi/src/lib/components/expandedFilter/ExpandedFilter.tsx b/packages/power-bi/src/lib/components/expandedFilter/ExpandedFilter.tsx
index ebf6cbf76..6f350a78e 100644
--- a/packages/power-bi/src/lib/components/expandedFilter/ExpandedFilter.tsx
+++ b/packages/power-bi/src/lib/components/expandedFilter/ExpandedFilter.tsx
@@ -25,16 +25,17 @@ export function ExpandedFilter({ controller }: ExpandedFilterProps): JSX.Element
   return (
     <StyledExpandedFilterWrapper>
       <StyledFilterItemsWrapper>
-        {slicerFilters
-          .filter((s) => visibleFilters.includes(s.type))
+        {visibleFilters
+          .map((s) => slicerFilters.find((x) => x.type === s))
+          .filter(Boolean)
           .map((group) => (
             <FilterItems
               controller={controller}
               handleOnChange={handleOnChange}
               handleOnSelectAll={controller.handleOnSelectAll}
               activeFilters={activeFilters}
-              group={group}
-              key={group.type}
+              group={group!}
+              key={group!.type}
             />
           ))}
       </StyledFilterItemsWrapper>
diff --git a/packages/power-bi/src/lib/components/skeleton/Skeleton.tsx b/packages/power-bi/src/lib/components/skeleton/Skeleton.tsx
new file mode 100644
index 000000000..fca120575
--- /dev/null
+++ b/packages/power-bi/src/lib/components/skeleton/Skeleton.tsx
@@ -0,0 +1,30 @@
+import styled from 'styled-components';
+
+export const Skeleton = styled.div<{ height: number; width: number }>`
+  width: ${({ width }) => `${width}px`};
+  height: ${({ height }) => `${height}px`};
+  cursor: progress;
+  background: linear-gradient(0.25turn, transparent, #fff, transparent), linear-gradient(#eee, #eee);
+  background-repeat: no-repeat;
+  background-size:
+    315px 250px,
+    315px 180px,
+    100px 100px,
+    225px 30px;
+  background-position:
+    -315px 0,
+    0 0,
+    0px 190px,
+    50px 195px;
+  border-radius: 5px;
+  animation: loading 1.5s infinite;
+  @keyframes loading {
+    to {
+      background-position:
+        315px 0,
+        0 0,
+        0 190px,
+        50px 195px;
+    }
+  }
+`;
diff --git a/packages/power-bi/src/lib/components/toggleHideFilterPopover.tsx/ToggleHideFilterPopover.tsx b/packages/power-bi/src/lib/components/toggleHideFilterPopover.tsx/ToggleHideFilterPopover.tsx
index b4a26a213..90b2384ab 100644
--- a/packages/power-bi/src/lib/components/toggleHideFilterPopover.tsx/ToggleHideFilterPopover.tsx
+++ b/packages/power-bi/src/lib/components/toggleHideFilterPopover.tsx/ToggleHideFilterPopover.tsx
@@ -4,6 +4,9 @@ import { useState, useRef } from 'react';
 import { ReactSortable } from 'react-sortablejs';
 import styled from 'styled-components';
 
+const joinListWithPreservedOrder = (list1: string[], list2: string[]) =>
+  list1.concat(list2).filter((v, i, a) => a.indexOf(v) === i);
+
 interface ShowHideFilterButtonProps {
   allFilters: string[];
   visibleFilters: string[];
@@ -22,7 +25,9 @@ export const ToggleHideFilterPopover = ({
   const [isOpen, setIsOpen] = useState(false);
   const ref = useRef<HTMLDivElement>(null);
 
-  const [list, setList] = useState<SortObject<string>[]>(allFilters.map((s) => ({ id: s, item: s })));
+  const listRef = useRef<SortObject<string>[]>(
+    joinListWithPreservedOrder(visibleFilters, allFilters).map((s) => ({ id: s, item: s }))
+  );
 
   const handleChange = (val: string) => {
     if (visibleFilters.includes(val)) {
@@ -33,7 +38,8 @@ export const ToggleHideFilterPopover = ({
   };
   const DraggableHandleSelector = 'globalDraggableHandle';
 
-  const updateList = () => setVisibleFilters(list.map((s) => s.item).filter((s) => visibleFilters.includes(s)));
+  const updateList = () =>
+    setVisibleFilters(listRef.current.map((s) => s.item).filter((s) => visibleFilters.includes(s)));
 
   return (
     <>
@@ -53,11 +59,13 @@ export const ToggleHideFilterPopover = ({
               <ReactSortable
                 animation={200}
                 handle={`.${DraggableHandleSelector}`}
-                list={list}
-                setList={setList}
+                list={listRef.current}
+                setList={(e) => {
+                  listRef.current = e;
+                }}
                 onEnd={updateList}
               >
-                {list.map(({ item }) => (
+                {listRef.current.map(({ item }) => (
                   <ItemWrapper className={DraggableHandleSelector} key={item}>
                     <Checkbox
                       size={2}
diff --git a/packages/workspace-fusion/src/lib/integrations/common/components/TabNavigation.tsx b/packages/workspace-fusion/src/lib/integrations/common/components/TabNavigation.tsx
index 628de2672..8e0603245 100644
--- a/packages/workspace-fusion/src/lib/integrations/common/components/TabNavigation.tsx
+++ b/packages/workspace-fusion/src/lib/integrations/common/components/TabNavigation.tsx
@@ -20,7 +20,7 @@ export const TabNavigation = () => {
       {!!leftIcons.length && (
         <>
           {leftIcons.map(({ Icon, name, type }) => (
-            <>
+            <Fragment key={name}>
               {pRef.current && (
                 <Fragment key={name}>
                   {type === 'button' ? (
@@ -34,7 +34,7 @@ export const TabNavigation = () => {
                   )}
                 </Fragment>
               )}
-            </>
+            </Fragment>
           ))}
           {(!!analyticsTabs.length || !!viewTabs.length || !!rightIcons.length) && <TabButtonDivider />}
         </>
@@ -73,7 +73,7 @@ export const TabNavigation = () => {
       {!!rightIcons.length && (
         <>
           {rightIcons.map(({ Icon, name, type }) => (
-            <>
+            <Fragment key={name}>
               {pRef.current && (
                 <Fragment key={name}>
                   {type === 'button' ? (
@@ -87,7 +87,7 @@ export const TabNavigation = () => {
                   )}
                 </Fragment>
               )}
-            </>
+            </Fragment>
           ))}
         </>
       )}

From b5778ed06444f2999c1e4e99552ec47ab19fdb40 Mon Sep 17 00:00:00 2001
From: Gustav Eikaas <guei@equinor.com>
Date: Mon, 20 Nov 2023 13:29:48 +0100
Subject: [PATCH 2/3] refactor: refactor powerbi filters

---
 .../src/lib/components/Filter/Filter.tsx      | 18 +++--------
 .../src/lib/hooks/useVisibleFilterGroups.ts   | 30 +++++++++++++++++++
 2 files changed, 34 insertions(+), 14 deletions(-)
 create mode 100644 packages/power-bi/src/lib/hooks/useVisibleFilterGroups.ts

diff --git a/packages/power-bi/src/lib/components/Filter/Filter.tsx b/packages/power-bi/src/lib/components/Filter/Filter.tsx
index 173a55190..180a6a30f 100644
--- a/packages/power-bi/src/lib/components/Filter/Filter.tsx
+++ b/packages/power-bi/src/lib/components/Filter/Filter.tsx
@@ -10,6 +10,7 @@ import { PowerBIQuickFilter } from '../QuickFilter/QuickFilter';
 import { search, playlist_add, drag_handle } from '@equinor/eds-icons';
 import { tokens } from '@equinor/eds-tokens';
 import { Skeleton } from '../skeleton/Skeleton';
+import { getVisibleFiltersFromLocalStorage, useVisibleFilters } from '../../hooks/useVisibleFilterGroups';
 
 Icon.add({ search, playlist_add, drag_handle });
 
@@ -32,7 +33,7 @@ export interface FilterController {
   setVisibleFilters: (visibleGroups: string[]) => void;
 }
 
-interface PowerBIFilterOptions {
+export interface PowerBIFilterOptions {
   defaultFilterGroupVisible?: string[];
 }
 
@@ -41,22 +42,11 @@ type PowerBIFilterProps = {
   options?: PowerBIFilterOptions;
 };
 
-const getVisibleFiltersFromLocalStorage = (reportId: string) => {
-  const value = localStorage.getItem(`${reportId}-filters`);
-  if (!value) return null;
-  const parsedValue = JSON.parse(value);
-  if (Array.isArray(parsedValue)) {
-    return parsedValue as string[];
-  }
-  return null;
-};
-
 export const PowerBIFilter = ({ report, options }: PowerBIFilterProps): JSX.Element | null => {
   const [activeFilters, setActiveFilters] = useState<Record<string, ActiveFilter[]>>({});
   const [slicerFilters, setSlicerFilters] = useState<PowerBiFilter[] | null>(null);
-  const [filterGroupVisible, setFilterGroupVisible] = useState<string[]>(
-    getVisibleFiltersFromLocalStorage(report.getId()) ?? options?.defaultFilterGroupVisible ?? []
-  );
+  const [filterGroupVisible, setFilterGroupVisible] = useVisibleFilters(report, options);
+
   const [isFilterExpanded, setIsFilterExpanded] = useState(false);
 
   const handleChangeGroup = async (filter: PowerBiFilter) => {
diff --git a/packages/power-bi/src/lib/hooks/useVisibleFilterGroups.ts b/packages/power-bi/src/lib/hooks/useVisibleFilterGroups.ts
new file mode 100644
index 000000000..aeeb5ac85
--- /dev/null
+++ b/packages/power-bi/src/lib/hooks/useVisibleFilterGroups.ts
@@ -0,0 +1,30 @@
+import { useState } from 'react';
+import { PowerBIFilterOptions } from '../components/Filter/Filter';
+import { Report } from 'powerbi-client';
+
+export const getVisibleFiltersFromLocalStorage = (reportId: string) => {
+  const value = localStorage.getItem(`${reportId}-filters`);
+  if (!value) return null;
+  const parsedValue = JSON.parse(value);
+  if (Array.isArray(parsedValue) && parsedValue.every((s) => typeof s === 'string')) {
+    return parsedValue as string[];
+  }
+  return null;
+};
+
+export const useVisibleFilters = (report: Report, options?: PowerBIFilterOptions) => {
+  const [filterGroupVisible, setFilterGroupVisible] = useState<string[]>(
+    getVisibleFiltersFromLocalStorage(report.getId()) ?? options?.defaultFilterGroupVisible ?? []
+  );
+
+  return [
+    filterGroupVisible,
+    (e: Parameters<typeof setFilterGroupVisible>[0]) => {
+      const reportId = report.getId();
+      if (reportId) {
+        localStorage.setItem(`${reportId}-filters`, JSON.stringify(e));
+      }
+      setFilterGroupVisible(e);
+    },
+  ] as const;
+};

From 0a3e4a3bc3d40e77d26d3c67f202b56cace465ea Mon Sep 17 00:00:00 2001
From: Gustav Eikaas <guei@equinor.com>
Date: Mon, 20 Nov 2023 13:39:44 +0100
Subject: [PATCH 3/3] chore: :bookmark: release

---
 packages/power-bi/package.json         | 2 +-
 packages/workspace-fusion/package.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/power-bi/package.json b/packages/power-bi/package.json
index 92721f10e..7dddd1bf2 100644
--- a/packages/power-bi/package.json
+++ b/packages/power-bi/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@equinor/workspace-powerbi",
-  "version": "1.0.8",
+  "version": "1.0.9",
   "type": "module",
   "sideEffects": false,
   "license": "MIT",
diff --git a/packages/workspace-fusion/package.json b/packages/workspace-fusion/package.json
index 72dfee909..72e3a90c5 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.3",
   "type": "module",
   "sideEffects": false,
   "license": "MIT",