Repository: GoogleChromeLabs/ps-analysis-tool
Pull request #108 (2023-08-17 12:26:21)

Refactor: Cookie filtering logic
## Description
The filtering logic for the cookie table data had some bugs where only the latest filter would apply. This PR aims to remove that bug and provide correct data post-filtering.

## Relevant Technical Choices

The filtering function has been split into 3 parts:
- A function which filters the cookie-based only on `retentionPeriod`.
- A function which filters the cookie based on all other filters except `rententionPeriod`.
- A function which controls all the possible combinations of the filters (i.e `retentionPeriod` and other filters case).

## Testing Instructions
- Pull this branch.
- In the terminal run 'npm i && npm run dev`.
- Open the browser got to `indianexpress.com`.
- Open the DevTools and click on privacy sandbox.
- Open the cookie dropdown and select any frame which has cookies.
- Clicking on the filtering Icon should open the filter UI.
- Toggling the filtering UI should give you the correct data in the cookie table.

## Screenshot/Screencast


https://github.com/GoogleChromeLabs/ps-analysis-tool/assets/59614577/17cb9f13-304d-4282-8fed-73cdd2257a2d




Patch:
diff --git a/package-lock.json b/package-lock.json
index 40106833..790ddeb1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20133,6 +20133,11 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/shallow-equal": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz",
+			"integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg=="
+		},
 		"node_modules/shallowequal": {
 			"version": "1.1.0",
 			"dev": true,
@@ -23195,6 +23200,7 @@
 				"re-resizable": "^6.9.9",
 				"react": "^18.2.0",
 				"react-dom": "^18.2.0",
+				"shallow-equal": "^3.1.0",
 				"simple-cookie": "^1.0.15",
 				"tldts": "^6.0.8",
 				"use-context-selector": "^1.4.1",
@@ -24563,6 +24569,7 @@
 				"re-resizable": "^6.9.9",
 				"react": "^18.2.0",
 				"react-dom": "^18.2.0",
+				"shallow-equal": "*",
 				"simple-cookie": "^1.0.15",
 				"tldts": "^6.0.8",
 				"use-context-selector": "^1.4.1",
@@ -36519,6 +36526,11 @@
 				"kind-of": "^6.0.2"
 			}
 		},
+		"shallow-equal": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz",
+			"integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg=="
+		},
 		"shallowequal": {
 			"version": "1.1.0",
 			"dev": true,
diff --git a/packages/extension/package.json b/packages/extension/package.json
index 727ae157..084fcb57 100644
--- a/packages/extension/package.json
+++ b/packages/extension/package.json
@@ -18,6 +18,7 @@
     "re-resizable": "^6.9.9",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "shallow-equal": "^3.1.0",
     "simple-cookie": "^1.0.15",
     "tldts": "^6.0.8",
     "use-context-selector": "^1.4.1",
diff --git a/packages/extension/src/utils/shallowEqual.ts b/packages/extension/src/utils/shallowEqual.ts
new file mode 100644
index 00000000..49ae656e
--- /dev/null
+++ b/packages/extension/src/utils/shallowEqual.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies
+ */
+import { shallowEqualArrays, shallowEqualObjects } from 'shallow-equal';
+
+export const shallowEqual = (a: unknown, b: unknown): boolean => {
+  if (a === b) {
+    return true;
+  }
+
+  if (Array.isArray(a) && Array.isArray(b)) {
+    return shallowEqualArrays(a, b);
+  }
+
+  if (typeof a === 'object' && typeof b === 'object') {
+    return shallowEqualObjects(a, b);
+  }
+
+  return false;
+};
diff --git a/packages/extension/src/utils/useContextSelector.ts b/packages/extension/src/utils/useContextSelector.ts
new file mode 100644
index 00000000..83a4acad
--- /dev/null
+++ b/packages/extension/src/utils/useContextSelector.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies
+ */
+import {
+  useContextSelector as useContextSelectorOrig,
+  type Context,
+} from 'use-context-selector';
+import { useRef, type MutableRefObject } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { shallowEqual } from './shallowEqual';
+
+interface EqualityFn {
+  (a: unknown, b: unknown): boolean;
+}
+
+/**
+ * This hook returns a part of the context value by selector.
+ * It will trigger a re-render only if the selected value has changed.
+ *
+ * By default, a shallow equals of the selected context value is used to
+ * determine if a re-render is needed.
+ * @param context Context.
+ * @param selector Returns a fragment of the context
+ * that the consumer is interested in.
+ * @param equalityFn Used to compare the
+ * selected context value. If the context fragment has not changed, a re-render
+ * will not be triggered. This is {shallowEqual} by default.
+ * @returns The selected context value fragment.
+ */
+function useContextSelector<T, S>(
+  context: Context<T>,
+  selector: (value: T) => S,
+  equalityFn: EqualityFn = shallowEqual
+) {
+  const ref: MutableRefObject<S | undefined> = useRef();
+
+  const equalityFnCallback = (state: T) => {
+    const selected = selector(state);
+    if (ref.current && equalityFn(ref.current, selected)) {
+      return ref.current;
+    }
+    ref.current = selected;
+    return selected;
+  };
+
+  // Update the selector fn to memoize the selected value by [equalityFn].
+  return useContextSelectorOrig(context, equalityFnCallback);
+}
+
+export default useContextSelector;
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
index 738ae76d..a27f9b11 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
@@ -16,7 +16,7 @@
 /**
  * External dependencies.
  */
-import React, { useMemo, useState } from 'react';
+import React, { useState } from 'react';
 import { Resizable } from 're-resizable';
 
 /**
@@ -35,8 +35,11 @@ const CookiesListing = () => {
     selectedFrame: state.selectedFrame,
   }));
 
-  const filteredCookies = useFilterManagementStore(
-    ({ state }) => state.filteredCookies
+  const { filteredCookies, cookiesAvailable } = useFilterManagementStore(
+    ({ state }) => ({
+      filteredCookies: state.filteredCookies,
+      cookiesAvailable: state.cookiesAvailable,
+    })
   );
 
   const [isFilterMenuOpen, setIsFilterMenuOpen] = useState<boolean>(false);
@@ -45,10 +48,6 @@ const CookiesListing = () => {
     setIsFilterMenuOpen((p) => !p);
   };
 
-  const cookiesAvailable = useMemo(() => {
-    return Boolean(filteredCookies.length);
-  }, [filteredCookies.length]);
-
   return (
     <div className="w-full h-full flex flex-col">
       <CookieTopBar
@@ -74,21 +73,23 @@ const CookiesListing = () => {
             }}
           >
             <div className="h-full flex">
-              {isFilterMenuOpen && cookiesAvailable && (
-                <Resizable
-                  minWidth="10%"
-                  maxWidth="50%"
-                  enable={{
-                    top: false,
-                    right: true,
-                    bottom: false,
-                    left: false,
-                  }}
-                  className="overflow-y-scroll overflow-x-hidden p-3"
-                >
-                  <FiltersList />
-                </Resizable>
-              )}
+              {cookiesAvailable
+                ? isFilterMenuOpen && (
+                    <Resizable
+                      minWidth="10%"
+                      maxWidth="50%"
+                      enable={{
+                        top: false,
+                        right: true,
+                        bottom: false,
+                        left: false,
+                      }}
+                      className="overflow-y-scroll overflow-x-hidden p-3"
+                    >
+                      <FiltersList />
+                    </Resizable>
+                  )
+                : null}
 
               <div className="flex-1 overflow-auto">
                 <CookieTable
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/constants.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/constants.ts
index 8498a7d4..813904e4 100644
--- a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/constants.ts
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/constants.ts
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 export const FILTER_MAPPING = [
   {
     name: 'Category',
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/index.tsx b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/index.tsx
index 67316749..2a225f49 100644
--- a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/index.tsx
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/index.tsx
@@ -16,7 +16,7 @@
 /**
  * External dependencies.
  */
-import { useContextSelector, createContext } from 'use-context-selector';
+import { createContext } from 'use-context-selector';
 import React, {
   type PropsWithChildren,
   useEffect,
@@ -24,12 +24,16 @@ import React, {
   useMemo,
   useCallback,
 } from 'react';
+/**
+ * Internal dependencies.
+ */
 import type { SelectedFilters, Filter } from './types';
 import { useCookieStore } from '../syncCookieStore';
 import type { CookieTableData } from '../../cookies.types';
 import getFilters from './utils/getFilters';
 import filterCookies from './utils/filterCookies';
 import { noop } from '../../../../utils/noop';
+import useContextSelector from '../../../../utils/useContextSelector';
 
 export interface filterManagementStore {
   state: {
@@ -37,6 +41,7 @@ export interface filterManagementStore {
     filters: Filter[];
     filteredCookies: CookieTableData[];
     searchTerm: string;
+    cookiesAvailable: boolean;
   };
   actions: {
     setSelectedFilters: (
@@ -52,6 +57,7 @@ const initialState: filterManagementStore = {
     filters: [],
     filteredCookies: [],
     searchTerm: '',
+    cookiesAvailable: false,
   },
   actions: {
     setSelectedFilters: noop,
@@ -91,6 +97,12 @@ export const Provider = ({ children }: PropsWithChildren) => {
     return _frameFilteredCookies;
   }, [cookies, selectedFrame, tabFrames]);
 
+  const cookiesAvailable = useMemo(() => {
+    return Boolean(
+      frameFilteredCookies && Object.keys(frameFilteredCookies).length
+    );
+  }, [frameFilteredCookies]);
+
   const filteredCookies = useMemo(() => {
     if (!selectedFrame) {
       return [];
@@ -148,6 +160,7 @@ export const Provider = ({ children }: PropsWithChildren) => {
         filters: selectedFrame ? filters[selectedFrame]?.filters || {} : [],
         filteredCookies,
         searchTerm,
+        cookiesAvailable,
       },
       actions: {
         setSelectedFilters,
@@ -161,6 +174,7 @@ export const Provider = ({ children }: PropsWithChildren) => {
       filteredCookies,
       searchTerm,
       setSelectedFilters,
+      cookiesAvailable,
     ]
   );
 
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookies.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookies.ts
index 1ed0c469..22029b5c 100644
--- a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookies.ts
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookies.ts
@@ -18,8 +18,8 @@
  */
 import type { SelectedFilters } from '../types';
 import type { CookieTableData } from '../../../cookies.types';
-import { FILTER_MAPPING, CUSTOM_FILTER_MAPPING } from '../constants';
-import getFilterValue from './getFilterValue';
+import filterCookiesWithRetentionPeriod from './filterCookiesWithRetentionPeriod';
+import filterCookiesWithoutRetentionPeriod from './filterCookiesWithoutRetentionPeriod';
 
 const filterCookies = (
   cookies: {
@@ -30,124 +30,59 @@ const filterCookies = (
 ): {
   [key: string]: CookieTableData;
 } => {
-  const filteredCookies: {
-    [key: string]: CookieTableData;
-  } = {};
-
-  if (!cookies || (!searchTerm && !Object.keys(selectedFilters).length)) {
-    return cookies;
+  // Case when both filters are present
+  if (
+    selectedFilters['retentionPeriod'] &&
+    Object.keys(selectedFilters).length > 1
+  ) {
+    const cookiesFilteredWithoutRetentionPeriod =
+      filterCookiesWithoutRetentionPeriod(cookies, selectedFilters, searchTerm);
+    return filterCookiesWithRetentionPeriod(
+      cookiesFilteredWithoutRetentionPeriod,
+      selectedFilters,
+      searchTerm
+    );
+  } else if (
+    selectedFilters['retentionPeriod'] &&
+    Object.keys(selectedFilters).length === 1
+  ) {
+    // Case when only retention period is present.
+    return filterCookiesWithRetentionPeriod(
+      cookies,
+      selectedFilters,
+      searchTerm
+    );
+  } else if (
+    !selectedFilters['retentionPeriod'] &&
+    Object.keys(selectedFilters).length >= 1
+  ) {
+    // All other filters except retention period is present.
+    return filterCookiesWithoutRetentionPeriod(
+      cookies,
+      selectedFilters,
+      searchTerm
+    );
   }
-
-  Object.entries(cookies).forEach(([cookieName, cookieData]) => {
-    let canShow = false;
-
-    const matchTerm = () => {
-      const lowerCaseTerm = searchTerm.toLowerCase();
-      return (
-        cookieName.toLowerCase().includes(lowerCaseTerm) ||
-        cookieData.parsedCookie.domain?.toLowerCase()?.includes(lowerCaseTerm)
-      );
-    };
-
-    if (Object.keys(selectedFilters).length) {
-      Object.entries(selectedFilters).forEach(([keys, selectedFilter]) => {
-        const customFilterKeys = Object.values(CUSTOM_FILTER_MAPPING).map(
-          (mapping) => mapping.keys
+  // This is when there are no filters but only search term.
+  if (searchTerm) {
+    const filteredCookies: {
+      [key: string]: CookieTableData;
+    } = {};
+    Object.entries(cookies).forEach(([cookieName, cookieData]) => {
+      const matchTerm = () => {
+        const lowerCaseTerm = searchTerm.toLowerCase();
+        return (
+          cookieName.toLowerCase().includes(lowerCaseTerm) ||
+          cookieData.parsedCookie.domain?.toLowerCase()?.includes(lowerCaseTerm)
         );
-
-        if (customFilterKeys.includes(keys)) {
-          selectedFilter.forEach((filterName) => {
-            if (canShow) {
-              return;
-            }
-
-            if (keys === CUSTOM_FILTER_MAPPING.retentionPeriod.keys) {
-              switch (filterName) {
-                case 'Session':
-                  canShow = cookieData.parsedCookie.expires === 0;
-                  break;
-                case 'less than a day':
-                  if (typeof cookieData.parsedCookie.expires === 'string') {
-                    const diff =
-                      Date.parse(cookieData.parsedCookie.expires) - Date.now();
-                    canShow = diff < 86400000;
-                  }
-                  break;
-                case 'a day to a week':
-                  if (typeof cookieData.parsedCookie.expires === 'string') {
-                    const diff =
-                      Date.parse(cookieData.parsedCookie.expires) - Date.now();
-                    canShow = diff >= 86400000 && diff < 604800000;
-                  }
-                  break;
-                case 'a week to a month':
-                  if (typeof cookieData.parsedCookie.expires === 'string') {
-                    const diff =
-                      Date.parse(cookieData.parsedCookie.expires) - Date.now();
-                    canShow = diff >= 604800000 && diff < 2629743833;
-                  }
-                  break;
-                case 'more than a month':
-                  if (typeof cookieData.parsedCookie.expires === 'string') {
-                    const diff =
-                      Date.parse(cookieData.parsedCookie.expires) - Date.now();
-                    canShow = diff >= 2629743833;
-                  }
-                  break;
-                default:
-              }
-            } else if (keys === CUSTOM_FILTER_MAPPING.scope.keys) {
-              switch (filterName) {
-                case 'Third Party':
-                  {
-                    canShow = !cookieData.isFirstParty;
-                  }
-                  break;
-                case 'First Party':
-                  {
-                    canShow = Boolean(cookieData.isFirstParty);
-                  }
-                  break;
-                default:
-              }
-            }
-          });
-        } else {
-          let value = getFilterValue(keys, cookieData);
-
-          const filterMap = FILTER_MAPPING.find(
-            (config) => config.keys === keys
-          );
-
-          if ('boolean' === filterMap?.type) {
-            value = value ? 'True' : 'False';
-          }
-
-          if (!value && filterMap?.default) {
-            value = filterMap.default;
-          }
-
-          canShow = selectedFilter?.has(value);
-        }
-      });
-
-      if (canShow) {
-        if (searchTerm) {
-          if (matchTerm()) {
-            filteredCookies[cookieName] = cookieData;
-          }
-        } else {
-          filteredCookies[cookieName] = cookieData;
-        }
-      }
-    } else {
+      };
       if (matchTerm()) {
         filteredCookies[cookieName] = cookieData;
       }
-    }
-  });
-
-  return filteredCookies;
+    });
+    return filteredCookies;
+  }
+  return cookies;
 };
 
 export default filterCookies;
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithRetentionPeriod.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithRetentionPeriod.ts
new file mode 100644
index 00000000..5b3b9645
--- /dev/null
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithRetentionPeriod.ts
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Internal dependencies.
+ */
+import type { SelectedFilters } from '../types';
+import type { CookieTableData } from '../../../cookies.types';
+import { CUSTOM_FILTER_MAPPING } from '../constants';
+
+const filterCookiesWithRetentionPeriod = (
+  cookies: {
+    [key: string]: CookieTableData;
+  },
+  selectedFilters: SelectedFilters,
+  searchTerm = ''
+): {
+  [key: string]: CookieTableData;
+} => {
+  const filteredCookies: {
+    [key: string]: CookieTableData;
+  } = {};
+
+  if (!cookies || (!searchTerm && !Object.keys(selectedFilters).length)) {
+    return cookies;
+  }
+
+  Object.entries(cookies).forEach(([cookieName, cookieData]) => {
+    let canShow = false;
+
+    const matchTerm = () => {
+      const lowerCaseTerm = searchTerm.toLowerCase();
+      return (
+        cookieName.toLowerCase().includes(lowerCaseTerm) ||
+        cookieData.parsedCookie.domain?.toLowerCase()?.includes(lowerCaseTerm)
+      );
+    };
+
+    if (Object.keys(selectedFilters).length) {
+      Object.entries(selectedFilters).forEach(([keys, selectedFilter]) => {
+        if (keys === CUSTOM_FILTER_MAPPING.retentionPeriod.keys) {
+          selectedFilter.forEach((retentionFilter) => {
+            if (canShow) {
+              return;
+            }
+            switch (retentionFilter) {
+              case 'Session':
+                canShow = cookieData.parsedCookie.expires === 0;
+                break;
+              case 'less than a day':
+                if (typeof cookieData.parsedCookie.expires === 'string') {
+                  const diff =
+                    Date.parse(cookieData.parsedCookie.expires) - Date.now();
+                  canShow = diff < 86400000;
+                }
+                break;
+              case 'a day to a week':
+                if (typeof cookieData.parsedCookie.expires === 'string') {
+                  const diff =
+                    Date.parse(cookieData.parsedCookie.expires) - Date.now();
+                  canShow = diff >= 86400000 && diff < 604800000;
+                }
+                break;
+              case 'a week to a month':
+                if (typeof cookieData.parsedCookie.expires === 'string') {
+                  const diff =
+                    Date.parse(cookieData.parsedCookie.expires) - Date.now();
+                  canShow = diff >= 604800000 && diff < 2629743833;
+                }
+                break;
+              case 'more than a month':
+                if (typeof cookieData.parsedCookie.expires === 'string') {
+                  const diff =
+                    Date.parse(cookieData.parsedCookie.expires) - Date.now();
+                  canShow = diff >= 2629743833;
+                }
+                break;
+              default:
+            }
+          });
+        }
+      });
+
+      if (canShow) {
+        if (searchTerm) {
+          if (matchTerm()) {
+            filteredCookies[cookieName] = cookieData;
+          }
+        } else {
+          filteredCookies[cookieName] = cookieData;
+        }
+      }
+    }
+  });
+  return filteredCookies;
+};
+
+export default filterCookiesWithRetentionPeriod;
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithoutRetentionPeriod.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithoutRetentionPeriod.ts
new file mode 100644
index 00000000..3e0a5798
--- /dev/null
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/filterCookiesWithoutRetentionPeriod.ts
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Internal dependencies.
+ */
+import type { SelectedFilters } from '../types';
+import type { CookieTableData } from '../../../cookies.types';
+import { FILTER_MAPPING, CUSTOM_FILTER_MAPPING } from '../constants';
+import getFilterValue from './getFilterValue';
+
+const filterCookiesWithoutRetentionPeriod = (
+  cookies: {
+    [key: string]: CookieTableData;
+  },
+  selectedFilters: SelectedFilters,
+  searchTerm = ''
+): {
+  [key: string]: CookieTableData;
+} => {
+  const filteredCookies: {
+    [key: string]: CookieTableData;
+  } = {};
+
+  if (!cookies || (!searchTerm && !Object.keys(selectedFilters).length)) {
+    return cookies;
+  }
+
+  Object.entries(cookies).forEach(([cookieName, cookieData]) => {
+    const canShow: boolean[] = [];
+
+    const matchTerm = () => {
+      const lowerCaseTerm = searchTerm.toLowerCase();
+      return (
+        cookieName.toLowerCase().includes(lowerCaseTerm) ||
+        cookieData.parsedCookie.domain?.toLowerCase()?.includes(lowerCaseTerm)
+      );
+    };
+
+    if (Object.keys(selectedFilters).length) {
+      Object.entries(selectedFilters).forEach(([keys, selectedFilter]) => {
+        if (
+          keys !== CUSTOM_FILTER_MAPPING.retentionPeriod.keys &&
+          keys !== CUSTOM_FILTER_MAPPING.scope.keys
+        ) {
+          let value = getFilterValue(keys, cookieData);
+          const filterMap = FILTER_MAPPING.find(
+            (config) => config.keys === keys
+          );
+
+          if ('boolean' === filterMap?.type) {
+            value = value ? 'True' : 'False';
+          }
+
+          if (!value && filterMap?.default) {
+            value = filterMap.default;
+          }
+
+          canShow.push(selectedFilter?.has(value));
+        } else if (keys === CUSTOM_FILTER_MAPPING.scope.keys) {
+          if (
+            selectedFilter.has('Third Party') &&
+            !selectedFilter.has('First Party')
+          ) {
+            canShow.push(!cookieData.isFirstParty);
+          } else if (
+            !selectedFilter.has('Third Party') &&
+            selectedFilter.has('First Party')
+          ) {
+            canShow.push(Boolean(cookieData.isFirstParty));
+          } else {
+            canShow.push(true);
+          }
+        }
+      });
+
+      if (canShow.length > 0 && !canShow.includes(false)) {
+        if (searchTerm) {
+          if (matchTerm()) {
+            filteredCookies[cookieName] = cookieData;
+          }
+        } else {
+          filteredCookies[cookieName] = cookieData;
+        }
+      }
+    } else {
+      if (matchTerm()) {
+        filteredCookies[cookieName] = cookieData;
+      }
+    }
+  });
+
+  return filteredCookies;
+};
+
+export default filterCookiesWithoutRetentionPeriod;
diff --git a/packages/extension/src/worker/createCookieObject.ts b/packages/extension/src/worker/createCookieObject.ts
index edf9d18b..e64eebbe 100644
--- a/packages/extension/src/worker/createCookieObject.ts
+++ b/packages/extension/src/worker/createCookieObject.ts
@@ -149,7 +149,7 @@ function parseAttributeValues(
     } else if (value === 'unspecified') {
       value = '';
     }
-    value = value || '';
+    value = ((value || '') as string).toLowerCase();
   }
 
   if (type === 'expires' && value !== 0) {


Test patch:
diff --git a/packages/extension/src/utils/test-data/frameFilteredCookies.ts b/packages/extension/src/utils/test-data/frameFilteredCookies.ts
new file mode 100644
index 00000000..d12d9366
--- /dev/null
+++ b/packages/extension/src/utils/test-data/frameFilteredCookies.ts
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Internal dependencies
+ */
+import { type CookieTableData } from '../../view/devtools/cookies.types';
+
+export const frameFilteredCookies: { [key: string]: CookieTableData } = {
+  LSOLH: {
+    analytics: {
+      category: 'Functional',
+      dataController: 'Google',
+      description: 'This cookie is for authentication with your Google account',
+      domain: 'accounts.google.com',
+      gdprUrl: 'https://privacy.google.com/take-control.html',
+      name: 'LSOLH',
+      platform: 'Google',
+      retention: '1 year',
+      wildcard: '0',
+    },
+    frameIdList: [0],
+    headerType: 'request',
+    isFirstParty: false,
+    parsedCookie: {
+      domain: 'accounts.google.com',
+      expires: String(new Date(Number(new Date()) + 5259487666).toISOString()),
+      httponly: false,
+      name: 'LSOLH',
+      path: '/',
+      samesite: 'none',
+      secure: true,
+      value:
+        '|_SVI_EMmpku6Lz4ADGBAiP01BRURIZl9xZWR4OHp5aUhCQ1h5RnVzcXFlb1NPUUVQeXVPZGlSTjBLeXdLMnRMOGpfd3JQZDk0STh3ZWJVNA_:|28192806:3705',
+    },
+    url: 'https://accounts.google.com/gsi/status?client_id=549796576131-8aoqmiheqf84a8bh3dl10ec2kp2v6e7p.apps.googleusercontent.com&as=ujHsjnl%2BJlozSZHE2Z6o2A',
+    isCookieSet: true,
+  },
+  NID: {
+    analytics: {
+      category: 'Marketing',
+      dataController: 'Google',
+      description:
+        'This cookies is used to collect website statistics and track conversion rates and Google ad personalisation',
+      domain: 'google.com',
+      gdprUrl: 'https://privacy.google.com/take-control.html',
+      name: 'NID',
+      platform: 'Google',
+      retention: '1 year',
+      wildcard: '0',
+    },
+    frameIdList: [3964, 3967, 0],
+    headerType: 'request',
+    isFirstParty: false,
+    parsedCookie: {
+      domain: '.google.com',
+      expires: String(new Date(Number(new Date()) + 5259487666).toISOString()),
+      httponly: true,
+      name: 'NID',
+      path: '/',
+      samesite: 'none',
+      secure: true,
+      value:
+        '511=Wrres5z7l_wUKKUzHhf2KIqAnj6papQrcGn1F12gPxd2SdxEGS-567t-hT7Yuzb8tJsENk8pT_9xCJ1UMPLbXKQk4mPUCtMp7VeFXp0yi8t0xPMvqgfUKKBpQwV8XmrveNPPwLmwxriIpyicaBYoJ-ZzfHvqnCZDya9rU_s_JwGk5JvIDTqIEYVQKzKJDyFQjiAINLhRFCmWOTWF7X3C3mYKVaUQrT4JLhZRAGEoj4Qz_ig5MbxpLB372-PjFrNofa5FvfmYWn3uBEFYm0UvAqhhyOcsND5eAf_iYYWufJAVGReSyfWZ0spF25g4TS-pUdU8l3zDJC-7',
+    },
+    url: 'https://www.google.com/recaptcha/api2/anchor?ar=1&k=6Le6JpwaAAAAAFkASVap1OUThl-lQSJC0r9kLl2I&co=aHR0cHM6Ly9ydGNhbXAuY29tOjQ0Mw..&hl=en&v=QybaJej5brGL8d7EvWmfKMZU&theme=light&size=invisible&badge=bottomright&cb=hztbhk44za6a',
+    isCookieSet: true,
+  },
+  _ga: {
+    analytics: {
+      category: 'Analytics',
+      dataController: 'Google',
+      description: 'ID used to identify users',
+      domain:
+        "google-analytics.com (3rd party) or advertiser's website domain (1st party)",
+      gdprUrl: 'https://privacy.google.com/take-control.html',
+      name: '_ga',
+      platform: 'Google Analytics',
+      retention: '2 years',
+      wildcard: '0',
+    },
+    frameIdList: [0],
+    headerType: 'request',
+    isFirstParty: true,
+    parsedCookie: {
+      domain: '.rtcamp.com',
+      expires: String(new Date(Number(new Date()) + 5259487666).toISOString()),
+      httponly: false,
+      name: '_ga',
+      path: '/',
+      samesite: '',
+      secure: false,
+      value: 'GA1.1.2118227284.1688917659',
+    },
+    url: 'https://rtcamp.com/wp-content/mu-plugins/jetpack-12.4/css/jetpack.css?ver=12.4',
+    isCookieSet: true,
+  },
+  _ga_7HKDVLRRV4: {
+    analytics: {
+      category: 'Analytics',
+      dataController: 'Google',
+      description: 'ID used to identify users',
+      domain:
+        "google-analytics.com (3rd party) or advertiser's website domain (1st party)",
+      gdprUrl: 'https://privacy.google.com/take-control.html',
+      name: '_ga_*',
+      platform: 'Google Analytics',
+      retention: '2 years',
+      wildcard: '1',
+    },
+    frameIdList: [0],
+    headerType: 'request',
+    isFirstParty: true,
+    parsedCookie: {
+      domain: '.rtcamp.com',
+      expires: String(new Date(Number(new Date()) + 5259487666).toISOString()),
+      httponly: false,
+      name: '_ga_7HKDVLRRV4',
+      path: '/',
+      samesite: '',
+      secure: false,
+      value: 'GS1.1.1692264828.19.1.1692264924.55.0.0',
+    },
+    url: 'https://rtcamp.com/wp-content/mu-plugins/jetpack-12.4/css/jetpack.css?ver=12.4',
+    isCookieSet: true,
+  },
+  _parsely_session: {
+    analytics: {
+      category: '',
+      dataController: '',
+      description: '',
+      domain: '',
+      gdprUrl: '',
+      name: '',
+      platform: '',
+      retention: '',
+      wildcard: '',
+    },
+    frameIdList: [0],
+    headerType: 'request',
+    isFirstParty: true,
+    parsedCookie: {
+      domain: '.rtcamp.com',
+      expires: String(new Date(Number(new Date()) + 86400000).toISOString()),
+      httponly: false,
+      name: '_parsely_session',
+      path: '/',
+      samesite: '',
+      secure: false,
+      value:
+        '{%22sid%22:8%2C%22surl%22:%22https://rtcamp.com/%22%2C%22sref%22:%22%22%2C%22sts%22:1692264828254%2C%22slts%22:1692261902478}',
+    },
+    url: 'https://rtcamp.com/wp-content/mu-plugins/jetpack-12.4/css/jetpack.css?ver=12.4',
+    isCookieSet: false,
+  },
+  _parsely_session_expiry: {
+    analytics: {
+      category: '',
+      dataController: '',
+      description: '',
+      domain: '',
+      gdprUrl: '',
+      name: '',
+      platform: '',
+      retention: '',
+      wildcard: '',
+    },
+    frameIdList: [0],
+    headerType: 'request',
+    isFirstParty: true,
+    parsedCookie: {
+      domain: '.rtcamp.com',
+      expires: 0,
+      httponly: false,
+      name: '_parsely_session_expiry',
+      path: '/',
+      samesite: '',
+      secure: false,
+      value:
+        '{%22sid%22:8%2C%22surl%22:%22https://rtcamp.com/%22%2C%22sref%22:%22%22%2C%22sts%22:1692264828254%2C%22slts%22:1692261902478}',
+    },
+    url: 'https://rtcamp.com/wp-content/mu-plugins/jetpack-12.4/css/jetpack.css?ver=12.4',
+    isCookieSet: false,
+  },
+};
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookies.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookies.ts
new file mode 100644
index 00000000..fc6e39db
--- /dev/null
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookies.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies.
+ */
+import '@testing-library/jest-dom';
+
+/**
+ * Internal dependencies.
+ */
+import filterCookies from '../filterCookies';
+import { frameFilteredCookies } from '../../../../../../utils/test-data/frameFilteredCookies';
+
+describe('FilterCookies:', () => {
+  it('Should return cookies as is if no filter is selected', () => {
+    expect(filterCookies(frameFilteredCookies, {}, '')).toBe(
+      frameFilteredCookies
+    );
+  });
+
+  it('Should return cookies with only those whose retention period is filter passing.', () => {
+    expect(
+      filterCookies(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set<string>(['less than a day']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _parsely_session: frameFilteredCookies['_parsely_session'],
+    });
+  });
+
+  it('Should return cookies with only those whose filter except retention period is set.', () => {
+    expect(
+      filterCookies(
+        frameFilteredCookies,
+        {
+          'analytics.category': new Set<string>(['Analytics']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _ga: frameFilteredCookies['_ga'],
+      _ga_7HKDVLRRV4: frameFilteredCookies['_ga_7HKDVLRRV4'],
+    });
+  });
+
+  it('Should return cookies with only those whose retention period and other filter is set.', () => {
+    expect(
+      filterCookies(
+        frameFilteredCookies,
+        {
+          'analytics.category': new Set<string>(['Analytics']),
+          retentionPeriod: new Set<string>(['more than a month']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _ga: frameFilteredCookies['_ga'],
+      _ga_7HKDVLRRV4: frameFilteredCookies['_ga_7HKDVLRRV4'],
+    });
+  });
+
+  it('Should search for cookie using name.', () => {
+    expect(filterCookies(frameFilteredCookies, {}, 'LSOLH')).toStrictEqual({
+      LSOLH: frameFilteredCookies['LSOLH'],
+    });
+  });
+});
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithRetentionPeriod.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithRetentionPeriod.ts
new file mode 100644
index 00000000..0dbc0bf2
--- /dev/null
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithRetentionPeriod.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies.
+ */
+import '@testing-library/jest-dom';
+
+/**
+ * Internal dependencies.
+ */
+import filterCookiesWithRetentionPeriod from '../filterCookiesWithRetentionPeriod';
+import { frameFilteredCookies } from '../../../../../../utils/test-data/frameFilteredCookies';
+
+describe('FilterCookiesWithoutRetentionPeriod:', () => {
+  it('Should return cookies as is if no filter is selected', () => {
+    expect(filterCookiesWithRetentionPeriod(frameFilteredCookies, {}, '')).toBe(
+      frameFilteredCookies
+    );
+  });
+
+  it('Should return cookies as if no cookies are passed', () => {
+    expect(filterCookiesWithRetentionPeriod({}, {}, '')).toStrictEqual({});
+  });
+
+  it('Should return cookies which are going to expire in less than a day', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['less than a day']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _parsely_session: frameFilteredCookies['_parsely_session'],
+    });
+  });
+
+  it('Should return Session cookies', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['Session']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _parsely_session_expiry: frameFilteredCookies['_parsely_session_expiry'],
+    });
+  });
+
+  it('Should return nothing for a day to a week cookies', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['a day to a week']),
+        },
+        ''
+      )
+    ).toStrictEqual({});
+  });
+
+  it('Should return nothing for a week to a month cookies', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['a week to a month']),
+        },
+        ''
+      )
+    ).toStrictEqual({});
+  });
+
+  it('Should return nothing for more than a month cookies', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['more than a month']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      LSOLH: frameFilteredCookies['LSOLH'],
+      NID: frameFilteredCookies['NID'],
+      _ga: frameFilteredCookies['_ga'],
+      _ga_7HKDVLRRV4: frameFilteredCookies['_ga_7HKDVLRRV4'],
+    });
+  });
+
+  it('Should search for cookies', () => {
+    expect(
+      filterCookiesWithRetentionPeriod(
+        frameFilteredCookies,
+        {
+          retentionPeriod: new Set(['Session']),
+        },
+        '_parsely_session_expiry'
+      )
+    ).toStrictEqual({
+      _parsely_session_expiry: frameFilteredCookies['_parsely_session_expiry'],
+    });
+  });
+});
diff --git a/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithoutRetentionPeriod.ts b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithoutRetentionPeriod.ts
new file mode 100644
index 00000000..88dfede0
--- /dev/null
+++ b/packages/extension/src/view/devtools/stateProviders/filterManagementStore/utils/tests/filterCookiesWithoutRetentionPeriod.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies.
+ */
+import '@testing-library/jest-dom';
+
+/**
+ * Internal dependencies.
+ */
+import filterCookiesWithoutRetentionPeriod from '../filterCookiesWithoutRetentionPeriod';
+import { frameFilteredCookies } from '../../../../../../utils/test-data/frameFilteredCookies';
+
+describe('FilterCookiesWithoutRetentionPeriod:', () => {
+  it('Should return cookies as is if no filter is selected', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(frameFilteredCookies, {}, '')
+    ).toBe(frameFilteredCookies);
+  });
+
+  it('Should return cookies as if no cookies are passed', () => {
+    expect(filterCookiesWithoutRetentionPeriod({}, {}, '')).toStrictEqual({});
+  });
+
+  it('Should return firstparty cookies', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(
+        frameFilteredCookies,
+        {
+          isFirstParty: new Set(['First Party']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      _parsely_session: frameFilteredCookies['_parsely_session'],
+      _parsely_session_expiry: frameFilteredCookies['_parsely_session_expiry'],
+      _ga_7HKDVLRRV4: frameFilteredCookies['_ga_7HKDVLRRV4'],
+      _ga: frameFilteredCookies['_ga'],
+    });
+  });
+
+  it('Should return thirdparty cookies', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(
+        frameFilteredCookies,
+        {
+          isFirstParty: new Set(['Third Party']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      LSOLH: frameFilteredCookies['LSOLH'],
+      NID: frameFilteredCookies['NID'],
+    });
+  });
+
+  it('Should return httponly cookies', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(
+        frameFilteredCookies,
+        {
+          'parsedCookie.httponly': new Set(['True']),
+        },
+        ''
+      )
+    ).toStrictEqual({
+      NID: frameFilteredCookies['NID'],
+    });
+  });
+
+  it('Should return firstparty cookies in result and search for cookies with _parsely_session', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(
+        frameFilteredCookies,
+        {
+          isFirstParty: new Set(['First Party']),
+        },
+        '_parsely_session'
+      )
+    ).toStrictEqual({
+      _parsely_session: frameFilteredCookies['_parsely_session'],
+      _parsely_session_expiry: frameFilteredCookies['_parsely_session_expiry'],
+    });
+  });
+
+  it('Should search for cookies', () => {
+    expect(
+      filterCookiesWithoutRetentionPeriod(frameFilteredCookies, {}, 'LSOLH')
+    ).toStrictEqual({ LSOLH: frameFilteredCookies['LSOLH'] });
+  });
+});
diff --git a/packages/extension/src/worker/tests/parseResponseCookieHeader.ts b/packages/extension/src/worker/tests/parseResponseCookieHeader.ts
index 11003b68..1a5c4911 100644
--- a/packages/extension/src/worker/tests/parseResponseCookieHeader.ts
+++ b/packages/extension/src/worker/tests/parseResponseCookieHeader.ts
@@ -46,7 +46,7 @@ describe('parseResponseCookieHeader', () => {
         secure: true,
         path: '/',
         domain: '.example.com',
-        samesite: 'None',
+        samesite: 'none',
         name: 'countryCode',
         value: 'IN',
       },
@@ -89,7 +89,7 @@ describe('parseResponseCookieHeader', () => {
         secure: true,
         path: '/',
         domain: '.example.com',
-        samesite: 'None',
+        samesite: 'none',
         name: 'test_cookie',
         value: 'bla',
       },
@@ -157,7 +157,7 @@ describe('parseResponseCookieHeader', () => {
         secure: true,
         path: '/',
         domain: '.google.com',
-        samesite: 'None',
+        samesite: 'none',
         name: '_ga_123',
         value: 'bla',
       },