Skip to content

Commit

Permalink
feat(pci.ai.notebook): add unit tests on order funnel (#14859)
Browse files Browse the repository at this point in the history
* feat(pci.ai.notebook): add unit tests on order funnel
REF:DATATR-1573
* feat(pci.ai.notebook): udpate data table (#14869)
* feat(pci.ai.notebook): udpate data table
* feat(pci.ai.notebook): udpate data table
* feat(pci.ai.notebook): udpate data table
* feat(pci.ai.notebook): fix pr comments
Signed-off-by: Arthur Bullet <[email protected]>
  • Loading branch information
abullet33 authored Jan 13, 2025
1 parent cb2dd87 commit 90f1a64
Show file tree
Hide file tree
Showing 44 changed files with 1,772 additions and 352 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const mockedCatalogPlanMonth: order.publicOrder.Plan = {
},
],
invoiceName: 'invoiceName1',
planCode: 'ai-notebook.flavorCPUId.minute.consumption',
planCode: 'ai-notebook.flavorBisCPUId.minute.consumption',
pricingType: order.cart.GenericProductPricingTypeEnum.consumption,
pricings: [mockedPricing],
product: 'product',
Expand All @@ -75,7 +75,7 @@ export const mockedCatalogStorageMonth: order.publicOrder.Plan = {
},
],
invoiceName: 'invoiceName2',
planCode: 'ai-notebook.flavorCPUId.minute.consumption',
planCode: 'ai-notebook.flavorGPUId.minute.consumption',
pricingType: order.cart.GenericProductPricingTypeEnum.consumption,
pricings: [mockedPricing],
product: 'product',
Expand All @@ -95,7 +95,7 @@ export const mockedCatalogStorageHour: order.publicOrder.Plan = {
},
],
invoiceName: 'invoiceName3',
planCode: 'ai-notebook.flavorCPUId.minute.consumption',
planCode: 'ai-notebook.flavorOtherCpuId.minute.consumption',
pricingType: order.cart.GenericProductPricingTypeEnum.consumption,
pricings: [mockedPricing],
product: 'product',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as ai from '@/types/cloud/project/ai';
export const mockedEditor: ai.capabilities.notebook.Editor = {
description: 'description',
docUrl: 'docURl',
id: 'editorId',
id: 'jupyterlab',
logoUrl: 'logo',
name: 'EditorName',
versions: ['version'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as ai from '@/types/cloud/project/ai';
export const mockedFramework: ai.notebook.Framework = {
description: 'description',
docUrl: 'docURl',
id: 'frameworkId',
id: 'one-for-all',
logoUrl: 'logo',
name: 'FrameworkName',
versions: ['version'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const mockedSuggestion: Suggestions[] = [
region: 'GRA',
ressources: {
nb: 1,
flavor: 'l4-1-gpu',
flavor: 'flavorCPUId',
},
framework: {
id: 'one-for-all',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { ReactElement, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { flexRender } from '@tanstack/react-table';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useDataTableContext } from './DataTableContext';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '../ui/button';

export const MENU_COLUMN_ID = 'actions';

interface DatatableProps<TData> {
renderRowExpansion?: (row: TData) => ReactElement | null;
}

export function DataTable<TData>({
renderRowExpansion,
}: DatatableProps<TData>) {
const { table, rows } = useDataTableContext();
const { t } = useTranslation('pci-databases-analytics/components/data-table');
const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({});

const toggleRowExpansion = (rowId: string) => {
setExpandedRows((prev) => ({
...prev,
[rowId]: !prev[rowId],
}));
};

const headerGroups = table.getHeaderGroups();
return (
<Table>
<TableHeader className="border bg-gray-50">
{headerGroups.map((headerGroup) => (
<TableRow key={headerGroup.id}>
{renderRowExpansion && (
<TableHead className="border-r-0 w-6"></TableHead>
)}
{headerGroup.headers.map((header, index) => {
const isEmptyHeader = header.id === MENU_COLUMN_ID;
// Get a reference to the previous header
const isEmptyNextHeader =
headerGroup.headers[index + 1]?.id === MENU_COLUMN_ID;
return (
<TableHead
key={header.id}
className={`border font-semibold text-primary-800 ${
isEmptyHeader || renderRowExpansion
? 'border-l-0' // Remove left border for empty headers and row extend column
: ''
} ${
isEmptyNextHeader
? 'border-r-0' // Remove right border from current column if next header is empty
: ''
}`}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody className="border">
{rows?.length ? (
rows.map((row) => (
<React.Fragment key={row.id}>
<TableRow data-state={row.getIsSelected() && 'selected'}>
{renderRowExpansion && (
<TableCell>
<Button
variant="ghost"
onClick={() => toggleRowExpansion(row.id)}
data-testid="table-row-expand-button"
>
{expandedRows[row.id] ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</TableCell>
)}
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
{expandedRows[row.id] && renderRowExpansion && (
<TableRow>
<TableCell colSpan={headerGroups[0].headers.length + 1}>
{renderRowExpansion(row.original as TData)}
</TableCell>
</TableRow>
)}
</React.Fragment>
))
) : (
<TableRow>
<TableCell
colSpan={headerGroups[0].headers.length}
className="h-24 text-center"
>
{t('noResult')}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
ColumnDef,
Row,
SortingState,
Table,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ReactNode, createContext, useContext, useMemo, useState } from 'react';
import { useColumnFilters } from './useColumnFilters.hook';
import { applyFilters } from '@/lib/filters';
import { ColumnFilter } from './DatatableDefaultFilterButton';
import { DataTable } from './DataTable';
import { DataTablePagination } from './DatatablePagination';

interface DataTableProviderProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
pageSize?: number;
itemNumber?: number;
filtersDefinition?: ColumnFilter[];
children?: ReactNode;
}

interface DataTableContextValue<TData> {
table: Table<TData>;
filtersDefinition?: ColumnFilter[];
columnFilters: ReturnType<typeof useColumnFilters>;
globalFilter: string;
data: TData[];
filteredData: TData[];
sorting: SortingState;
rows: Row<TData>[];
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const DataTableContext = createContext<DataTableContextValue<any> | null>(null);

export function DataTableProvider<TData, TValue>({
columns,
data,
pageSize,
filtersDefinition,
children,
}: DataTableProviderProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([
{
id: columns[0]?.id as string,
desc: false,
},
]);
const [globalFilter, setGlobalFilter] = useState<string>('');
const columnFilters = useColumnFilters();

const filteredData = useMemo(
() => applyFilters(data || [], columnFilters.filters) as TData[],
[columnFilters.filters, data],
);
const table = useReactTable({
data: filteredData,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
globalFilter,
},
initialState: {
pagination: { pageSize: pageSize ?? 5 },
},
onGlobalFilterChange: (e) => {
setGlobalFilter(e);
},
globalFilterFn: 'auto',
});

const rows = useMemo(() => table.getRowModel()?.rows, [
table,
globalFilter,
columnFilters.filters,
data,
sorting,
]);

const contextValue: DataTableContextValue<TData> = {
table,
filtersDefinition,
columnFilters,
globalFilter,
data,
filteredData,
sorting,
rows,
};

return (
<DataTableContext.Provider value={contextValue}>
{children || (
<>
<DataTable />
<DataTablePagination />
</>
)}
</DataTableContext.Provider>
);
}

export function useDataTableContext<TData>() {
const context = useContext<DataTableContextValue<TData>>(DataTableContext);
if (!context) {
throw new Error(
'useDataTableContext must be used within a DataTableProvider',
);
}
return context as DataTableContextValue<TData>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ReactNode } from 'react';

const DatatableAction = ({ children }: { children: ReactNode }) => {
return <>{children || <></>}</>;
};

export default DatatableAction;
Loading

0 comments on commit 90f1a64

Please sign in to comment.