Repository: GoogleChromeLabs/ps-analysis-tool (ts)

Issue:
Implement sorting functionality for cookie table columns.
## Feature Request

**Description:**
Add sorting functionality for each column based on its values. 

**User Story:**
As a developer/website owner/web user I want to be able to sort the cookies table based on its values when clicking on its header arrow.

**Acceptance Criteria:**

- [ ] Add an arrow to the table header cells
- [ ] Clicking on the header cell should sort the column based on its values.



Pull request:
Feature: Add Column Sorting Functionality
## Description
This pull request introduces new feature that enhances the Table components with column sorting functionality. One can sort columns in ascending or descending order, offering better control and improved data organisation within the tables.



<!-- What do we want to achieve with this PR? -->

## Relevant Technical Choices


<!-- Please describe your changes. -->

## Testing Instructions
- Open the terminal in the root directory of the project.
- Run the command npm run dev, and the extension' Webpack development will spin up.
- Open website indianexpress.com, open devtools panel and select Privacy Sandbox tab.
- Open `CookiesPanel`, inside the `CookieTable` component click on any table header.
- Clicks on table header will make the table be sorted into ascending or descending pattern based on the header column's values and number of clicks.
<!--
How can the changes in this PR be verified?
Please provide step-by-step instructions to test the changes.
-->

## Additional Information:

<!-- Any other information. -->

## Screenshot/Screencast


https://github.com/GoogleChromeLabs/ps-analysis-tool/assets/58820001/59282865-a8a3-4410-981c-d8b6b01ceb53



<!-- Please provide Screenshot/Screencast, if applicable -->

---

<!--
Example:

Fixes #123
Partially addresses #22
See #834
-->

Fixes #69 


Patch:
diff --git a/packages/extension/icons/arrow-down.svg b/packages/extension/icons/arrow-down.svg
new file mode 100644
index 00000000..7626356d
--- /dev/null
+++ b/packages/extension/icons/arrow-down.svg
@@ -0,0 +1,3 @@
+<svg width="7" height="5" viewBox="0 0 7 5" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.59998 0H0.0999756L3.59998 5L6.59998 0Z" fill="#6E6E6E"/>
+</svg>
diff --git a/packages/extension/src/view/devtools/components/table/tableHeader/headerCell.tsx b/packages/extension/src/view/devtools/components/table/tableHeader/headerCell.tsx
index 9c176385..fa3012b4 100644
--- a/packages/extension/src/view/devtools/components/table/tableHeader/headerCell.tsx
+++ b/packages/extension/src/view/devtools/components/table/tableHeader/headerCell.tsx
@@ -25,6 +25,7 @@ import { flexRender, type Header } from '@tanstack/react-table';
  */
 import type { TableData } from '..';
 import HeaderResizer from './headerResizer';
+import ArrowDown from '../../../../../../icons/arrow-down.svg';
 
 interface HeaderCellProps {
   header: Header<TableData, unknown>;
@@ -35,7 +36,9 @@ const HeaderCell = ({ header }: HeaderCellProps) => {
     <th
       colSpan={header.colSpan}
       style={{ maxWidth: header.getSize() }}
+      onClick={header.column.getToggleSortingHandler()}
       className="border-x border-american-silver relative hover:bg-gainsboro select-none touch-none font-normal"
+      data-testid="header-cell"
     >
       <div className="w-full h-full flex items-center justify-between">
         <p className="px-1 py-px truncate text-xs">
@@ -43,6 +46,12 @@ const HeaderCell = ({ header }: HeaderCellProps) => {
             ? null
             : flexRender(header.column.columnDef.header, header.getContext())}
         </p>
+        <p className="mr-2 scale-125">
+          {{
+            asc: <ArrowDown className="transform rotate-180" />,
+            desc: <ArrowDown />,
+          }[header.column.getIsSorted() as string] ?? null}
+        </p>
       </div>
       <HeaderResizer header={header} />
     </th>


Test patch:
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
index b8ed72b1..5a27a600 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
@@ -185,6 +185,34 @@ describe('CookieTab', () => {
     expect((await screen.findAllByText('Marketing')).length).toBe(2);
   });
 
+  it('should sort cookies by name', async () => {
+    render(<CookieTab />);
+
+    const headerCell = (await screen.findAllByTestId('header-cell'))[0];
+    fireEvent.click(headerCell);
+
+    const firstRow = (await screen.findAllByTestId('body-row'))[0];
+
+    expect(
+      await within(firstRow).findByText('KRTBCOOKIE_290')
+    ).toBeInTheDocument();
+
+    const lastRow = (await screen.findAllByTestId('body-row'))[3];
+    expect(await within(lastRow).findByText('pubsyncexp')).toBeInTheDocument();
+
+    fireEvent.click(headerCell);
+
+    const firstRowAfterReverse = (await screen.findAllByTestId('body-row'))[0];
+    expect(
+      await within(firstRowAfterReverse).findByText('pubsyncexp')
+    ).toBeInTheDocument();
+
+    const lastRowAfterReverse = (await screen.findAllByTestId('body-row'))[3];
+    expect(
+      await within(lastRowAfterReverse).findByText('KRTBCOOKIE_290')
+    ).toBeInTheDocument();
+  });
+
   it('should render a cookie card with placeholder text when no cookie is selected', async () => {
     mockUseContentPanelStore.mockReturnValue({
       selectedCookie: null,