Skip to content

Commit

Permalink
Merge pull request #857 from tailwarden/develop
Browse files Browse the repository at this point in the history
v3.0.19 release 🚀
  • Loading branch information
mlabouardy authored Jun 16, 2023
2 parents 1ce65f8 + bfa89c9 commit c4100e9
Show file tree
Hide file tree
Showing 14 changed files with 494 additions and 61 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* @mlabouardy @ShubhamPalriwala
README.md @jakepage91
CONTRIBUTING.md @jakepage91
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ function useResourcesManagerChart({
labels: sortByDescendingCosts?.map(item => item.label),
datasets: [
{
data: sortByDescendingCosts?.map(item =>
item.total
) as number[],
data: sortByDescendingCosts?.map(item => item.total) as number[],
backgroundColor: colors,
borderColor: '#FFFFFF',
borderWidth: 3,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useRouter } from 'next/router';
import formatNumber from '../../../utils/formatNumber';
import Button from '../../button/Button';
import { InventoryStats } from '../hooks/useInventory/types/useInventoryTypes';

Expand All @@ -14,22 +13,18 @@ type InventoryTableBulkActionsProps = {

function InventoryTableBulkActions({
bulkItems,
inventoryStats,
openBulkModal,
query,
hideResourceFromCustomView,
hideResourcesLoading
}: InventoryTableBulkActionsProps) {
const router = useRouter();
const resourceText = bulkItems.length > 1 ? 'resources' : 'resource';
return (
<>
{bulkItems && bulkItems.length > 0 && (
<div className="border-purplin-650 sticky bottom-0 flex w-full items-center justify-between bg-white py-4 px-6 text-sm font-medium shadow-[0px_-2px_4px_rgba(0,0,0,0.05)]">
<p className="text-black-900">
{bulkItems.length} {bulkItems.length > 1 ? 'resources' : 'resource'}{' '}
{inventoryStats &&
!query &&
`out of ${formatNumber(inventoryStats.resources)}`}{' '}
{bulkItems.length} {resourceText} {''}
selected
</p>
<div className="flex gap-4">
Expand All @@ -38,12 +33,8 @@ function InventoryTableBulkActions({
style="primary"
onClick={() => openBulkModal(bulkItems)}
>
Manage tags
<span className="ml-1 flex items-center justify-center rounded-lg bg-white/10 py-1 px-2 text-xs">
{formatNumber(bulkItems.length)}
</span>
Tag {resourceText}
</Button>

{router.query.view && (
<Button
size="sm"
Expand All @@ -52,9 +43,6 @@ function InventoryTableBulkActions({
loading={hideResourcesLoading}
>
Hide from view
<span className="ml-1 flex items-center justify-center rounded-lg bg-primary/10 py-1 px-2 text-xs">
{formatNumber(bulkItems.length)}
</span>
</Button>
)}
</div>
Expand Down
130 changes: 130 additions & 0 deletions dashboard/components/select-checkbox/SelectCheckbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { render, fireEvent } from '@testing-library/react';
import SelectCheckbox from './SelectCheckbox';

jest.mock('./hooks/useSelectCheckbox', () => ({
__esModule: true,
default: jest.fn(() => ({
listOfExcludableItems: ['Item 1', 'Item 2', 'Item 3'],
error: null
}))
}));

describe('SelectCheckbox', () => {
it('renders the label correctly', () => {
const { getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

expect(getByText('Test Label')).toBeInTheDocument();
});

it('opens the dropdown when clicked', () => {
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));

expect(getByText('Item 1')).toBeInTheDocument();
expect(getByText('Item 2')).toBeInTheDocument();
expect(getByText('Item 3')).toBeInTheDocument();
});

it('closes the dropdown when clicked outside', () => {
const { getByRole, queryByText, getByTestId } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));
expect(queryByText('Item 1')).toBeInTheDocument();

fireEvent.click(getByTestId('overlay'));
expect(queryByText('Item 1')).toBeNull();
});

it('selects and excludes items', () => {
const setExcludeMock = jest.fn();
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={setExcludeMock}
/>
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('checkbox', { name: 'Item 1' }));
fireEvent.click(getByRole('checkbox', { name: 'Item 3' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith(['Item 1', 'Item 3']);
});

it('selects and deselects all items with "Exclude All" checkbox', () => {
const setExcludeMock = jest.fn();
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={setExcludeMock}
/>
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('checkbox', { name: 'Exclude All' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith(['Item 1', 'Item 2', 'Item 3']);

fireEvent.click(getByRole('checkbox', { name: 'Exclude All' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith([]);
});

it('filters the list of items based on search input', () => {
const { getByRole, getByPlaceholderText, queryByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));
const searchInput = getByPlaceholderText('Search');

fireEvent.change(searchInput, { target: { value: 'Item 1' } });
expect(queryByText('Item 2')).toBeNull();
expect(queryByText('Item 3')).toBeNull();
expect(queryByText('Item 1')).toBeInTheDocument();

fireEvent.change(searchInput, { target: { value: 'Item' } });
expect(queryByText('Item 1')).toBeInTheDocument();
expect(queryByText('Item 2')).toBeInTheDocument();
expect(queryByText('Item 3')).toBeInTheDocument();

fireEvent.change(searchInput, { target: { value: 'Non-matching' } });
expect(queryByText('Item 1')).toBeNull();
expect(queryByText('Item 2')).toBeNull();
expect(queryByText('Item 3')).toBeNull();
});
});
113 changes: 80 additions & 33 deletions dashboard/components/select-checkbox/SelectCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Button from '../button/Button';
import Checkbox from '../checkbox/Checkbox';
import { ResourcesManagerQuery } from '../dashboard/components/resources-manager/hooks/useResourcesManager';
import useSelectCheckbox from './hooks/useSelectCheckbox';
import ChevronDownIcon from '../icons/ChevronDownIcon';

export type SelectCheckboxProps = {
label: string;
Expand Down Expand Up @@ -41,6 +42,14 @@ function SelectCheckbox({
}
}

function handleCheckAll(e: ChangeEvent<HTMLInputElement>) {
if (e.currentTarget.checked) {
setCheckedItems(listOfExcludableItems);
} else {
setCheckedItems([]);
}
}

function submit() {
setExclude(checkedItems);
}
Expand All @@ -55,6 +64,12 @@ function SelectCheckbox({

return (
<div className="relative">
<div
className="pointer-events-none absolute right-4
bottom-[1.15rem] text-black-900 transition-all"
>
<ChevronDownIcon width={24} height={24} />
</div>
<button
onClick={toggle}
className={`h-[60px] w-full overflow-hidden rounded text-left outline hover:outline-black-200 focus:outline-2 focus:outline-primary ${
Expand Down Expand Up @@ -87,37 +102,43 @@ function SelectCheckbox({
{isOpen && (
<>
<div
data-testid="overlay"
onClick={toggle}
className="fixed inset-0 z-20 hidden animate-fade-in bg-transparent opacity-0 sm:block"
></div>
<div className="absolute top-[4.15rem] z-[21] w-full rounded-lg border border-black-200 bg-white shadow-lg">
<div className="relative overflow-hidden rounded-lg rounded-b-none">
<div className="absolute top-[4.15rem] z-[21] w-full rounded-lg border border-black-100 bg-white shadow-lg">
<div className="relative m-4 ">
{!search ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className="absolute top-[1.125rem] left-4"
className="absolute top-[0.5rem] left-3"
>
<path
stroke="currentColor"
d="M17 17L21 21"
stroke="#ababab"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M11.5 21a9.5 9.5 0 100-19 9.5 9.5 0 000 19zM22 22l-2-2"
></path>
/>
<path
d="M19 11C19 15.4183 15.4183 19 11 19C6.58172 19 3 15.4183 3 11C3 6.58172 6.58172 3 11 3C15.4183 3 19 6.58172 19 11Z"
stroke="#ababab"
strokeWidth="2"
/>
</svg>
) : (
<svg
onClick={() => setSearch('')}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className="absolute top-[1.175rem] left-4 cursor-pointer"
className="absolute top-[0.5rem] left-3 cursor-pointer"
>
<path
stroke="currentColor"
Expand All @@ -134,7 +155,7 @@ function SelectCheckbox({
onChange={e => setSearch(e.target.value)}
type="text"
placeholder="Search"
className="w-full border-b border-black-200/50 bg-white py-4 pl-10 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none"
className="h-10 w-full rounded-md border border-black-200/50 bg-white py-4 pl-10 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none"
autoFocus
/>
</div>
Expand All @@ -145,26 +166,52 @@ function SelectCheckbox({
)}
{!error && (
<>
<div className="flex max-h-[12rem] flex-col gap-3 overflow-auto p-4">
{resources.map((resource, idx) => (
<div key={idx} className="flex items-center gap-2 text-sm">
<Checkbox
id={resource}
onChange={e => handleChange(e, resource)}
checked={
!!checkedItems.find(value => value === resource)
}
/>
<label htmlFor={resource} className="w-full">
{resource}
</label>
</div>
))}
{resources.length === 0 && (
<p className="text-sm text-black-400">
There are no results for {search}
</p>
)}
{!search && (
<div className="m-4 ml-6 flex items-center gap-2 text-sm">
<Checkbox
id="all"
onChange={e => {
handleCheckAll(e);
}}
checked={checkedItems.length === resources.length}
/>
<label
htmlFor="all"
className="w-full text-sm text-black-400"
>
Exclude All
</label>
</div>
)}
<hr className="m-4 mb-0 h-px border-t-0 bg-neutral-100 opacity-100 dark:opacity-50" />
<div className="scrollbar mt-2 mb-2 mr-3 overflow-auto">
<div className="mt-2 flex max-h-[12rem] flex-col gap-3 p-4 pb-4 pt-0 pl-6">
{resources.map((resource, idx) => (
<div
key={idx}
className="flex items-center gap-2 text-sm"
>
<Checkbox
id={resource}
onChange={e => handleChange(e, resource)}
checked={
!!checkedItems.find(value => value === resource)
}
/>
<label
htmlFor={resource}
className="w-full text-black-400"
>
{resource}
</label>
</div>
))}
{resources.length === 0 && (
<p className="text-sm text-black-400">
There are no results for {search}
</p>
)}
</div>
</div>
<div className="flex flex-col border-t border-black-200/50 p-4">
<Button onClick={submit}>Apply</Button>
Expand Down
4 changes: 2 additions & 2 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c4100e9

Please sign in to comment.