Skip to content

Commit

Permalink
feat(*): pci-users app in reactjs
Browse files Browse the repository at this point in the history
Add users & roles pci µapp 
Updates gitignore
Make img in onboarding component optional 
Add guides-header, action-banner unit test
Bump manager-core-api
ref: DTCORE-1844

Signed-off-by: Florian Renaut <[email protected]>
Co-authored-by: Mohammed Hamdoune <[email protected]>
Co-authored-by: Yoann Fievez <[email protected]>
Co-authored-by: LIDRISSI Hamid <[email protected]>
  • Loading branch information
4 people authored Apr 10, 2024
1 parent 7e93099 commit 07959d4
Show file tree
Hide file tree
Showing 153 changed files with 7,345 additions and 54 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
node_modules
cache
dist
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
'/node_modules/',
'/apps/pci-vouchers/',
'/apps/pci-ssh-keys/',
'/apps/pci-users/',
],
moduleNameMapper: {
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
Expand Down
4 changes: 3 additions & 1 deletion packages/manager-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@ovh-ux/manager-core-api": "^0.7.0",
"@ovhcloud/ods-common-core": "17.2.1",
"@ovhcloud/ods-common-testing": "17.2.1",
"@ovhcloud/ods-common-theming": "17.2.1",
Expand Down Expand Up @@ -79,7 +80,8 @@
"vite": "4.4.9",
"vite-plugin-dts": "3.5.1",
"vitest": "0.34.1",
"zustand": "^4.5.0"
"zustand": "^4.5.0",
"element-internals-polyfill": "^1.3.10"
},
"optionalDependencies": {
"@ovh-ux/manager-react-shell-client": "^0.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ export function ActionBanner({ message, cta, onClick }: ActionBannerProps) {
></span>
</OsdsText>
<OsdsButton
data-testid="cta"
className="sm:mt-0 mt-4 sm:ml-4 ml-0"
size={ODS_BUTTON_SIZE.sm}
color={ODS_THEME_COLOR_INTENT.primary}
onClick={() => {
if (onClick) {
onClick();
}
}}
onClick={onClick}
>
{cta}
</OsdsButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ActionBanner, ActionBannerProps } from './action-banner.component';
import { render } from '../../utils/test.provider';

const renderComponent = (props: ActionBannerProps) => {
return render(<ActionBanner {...props} />);
};

describe('ActionBanner tests', () => {
it('should display message', () => {
renderComponent({
message: 'hello world',
cta: 'custom action',
onClick: () => {},
});
expect(screen.getAllByText('hello world')).not.toBeNull();
});
it('should have a working call to action button', () => {
const onClick = jest.fn();
renderComponent({
message: 'hello world',
cta: 'custom action',
onClick,
});
expect(screen.getAllByText('custom action')).not.toBeNull();
const cta = screen.queryByTestId('cta');
expect(onClick).not.toHaveBeenCalled();
fireEvent.click(cta);
expect(onClick).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useNavigation } from '@ovh-ux/manager-react-shell-client';
import { fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import {
isDiscoveryProject,
PciDiscoveryBanner,
PciDiscoveryBannerProps,
} from './pci-discovery-banner.component';
import { render } from '../../../utils/test.provider';

jest.mock('@ovh-ux/manager-react-shell-client', () => {
const navigateTo = jest.fn();
return {
useNavigation: () => {
return {
navigateTo,
};
},
};
});

const renderComponent = (props: PciDiscoveryBannerProps) => {
return render(<PciDiscoveryBanner {...props} />);
};

describe('PciDiscoveryBanner tests', () => {
it('should navigate to project activation when clicking on cta', () => {
const { navigateTo } = useNavigation();
renderComponent({
projectId: '123',
});
const cta = screen.queryByTestId('cta');
expect(navigateTo).not.toHaveBeenCalled();
fireEvent.click(cta);
expect(navigateTo).toHaveBeenCalledWith(
'public-cloud',
'#/pci/projects/123/activate',
{},
);
});
it('should identify discovery projects', () => {
expect(isDiscoveryProject({ planCode: 'foo' })).toBeFalsy();
expect(isDiscoveryProject({ planCode: undefined })).toBeFalsy();
expect(isDiscoveryProject({ planCode: 'project.discovery' })).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const isDiscoveryProject = ({ planCode }: { planCode: string }) => {
return planCode === DISCOVERY_PROJECT_PLANCODE;
};

interface PciDiscoveryBannerProps {
export interface PciDiscoveryBannerProps {
projectId: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,10 @@ export const Datagrid = <T extends unknown>({
</tbody>
</table>
</div>
{pagination && (
{items?.length > 0 && pagination ? (
<OsdsPagination
defaultCurrentPage={pagination.pageIndex + 1}
className={'flex xs:justify-center md:justify-end'}
className={'flex xs:justify-start md:justify-end'}
total-items={totalItems}
total-pages={pageCount}
default-items-per-page={pagination.pageSize}
Expand Down Expand Up @@ -241,6 +241,8 @@ export const Datagrid = <T extends unknown>({
{t('common_pagination_results')}
</span>
</OsdsPagination>
) : (
<div className="mb-6" aria-hidden="true"></div>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PaginationState } from '@tanstack/react-table';

/* List of allowed page sizes */
export const PAGE_SIZES = [10, 25, 50, 100, 300];

export const DEFAULT_PAGINATION: PaginationState = {
pageIndex: 0,
pageSize: PAGE_SIZES[0],
};
11 changes: 11 additions & 0 deletions packages/manager-components/src/components/datagrid/useDatagrid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from 'react';
import { ColumnSort, PaginationState } from '@tanstack/react-table';
import { DEFAULT_PAGINATION } from './datagrid.contants';

export const useDataGrid = (defaultSorting: ColumnSort = undefined) => {
const [pagination, setPagination] = useState<PaginationState>(
DEFAULT_PAGINATION,
);
const [sorting, setSorting] = useState<ColumnSort>(defaultSorting);
return { pagination, setPagination, sorting, setSorting };
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import { ColumnSort, PaginationState } from '@tanstack/react-table';
import { useSearchParams } from 'react-router-dom';
import { DEFAULT_PAGINATION, PAGE_SIZES } from './datagrid.contants';

/**
* This hooks allows to store and synchronize the datagrid pagination & sorting
* state within URL search parameters. Thus the user is able to refresh his page
* without loosing his current pagination and column sorting state.
*/

/* List of allowed page sizes */
const PAGE_SIZES = [10, 25, 50, 100, 300];

/* Convert URL search params to plain object */
const getSearchParamsObject = (search: URLSearchParams) =>
Object.fromEntries([...search.entries()]);

/* Parse pagination from URL search params */
const parsePagination = (params: URLSearchParams): PaginationState => {
const pagination: PaginationState = {
pageIndex: 0,
pageSize: PAGE_SIZES[0],
};
const pagination = { ...DEFAULT_PAGINATION };
if (params.has('page')) {
let pageIndex = parseInt(params.get('page'), 10) - 1;
if (Number.isNaN(pageIndex) || pageIndex < 0) pageIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { useMemo, useState } from 'react';
import { Filter, FilterComparator } from '@ovh-ux/manager-core-api';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import {
ODS_BUTTON_SIZE,
ODS_INPUT_TYPE,
ODS_TEXT_LEVEL,
} from '@ovhcloud/ods-components';
import {
OsdsButton,
OsdsFormField,
OsdsInput,
OsdsSelect,
OsdsSelectOption,
OsdsText,
} from '@ovhcloud/ods-components/react';
import { useTranslation } from 'react-i18next';
import './translations';

type ColumnFilter = {
id: string;
label: string;
comparators: FilterComparator[];
};

export type FilterAddProps = {
columns: ColumnFilter[];
onAddFilter: (filter: Filter, column: ColumnFilter) => void;
};

export function FilterAdd({ columns, onAddFilter }: Readonly<FilterAddProps>) {
const { t } = useTranslation('filters');

const [selectedId, setSelectedId] = useState(columns?.[0]?.id || '');
const [selectedComparator, setSelectedComparator] = useState(
columns?.[0]?.comparators?.[0] || FilterComparator.IsEqual,
);
const [value, setValue] = useState('');

const selectedColumn = useMemo(
() => columns.find(({ id }) => selectedId === id),
[columns, selectedId],
);

const submitAddFilter = () => {
onAddFilter(
{
key: selectedId,
comparator: selectedComparator,
value,
},
selectedColumn,
);
setValue('');
};

return (
<>
<OsdsFormField>
<div slot="label">
<OsdsText
level={ODS_TEXT_LEVEL.heading}
color={ODS_THEME_COLOR_INTENT.text}
>
{t('common_criteria_adder_column_label')}
</OsdsText>
</div>
<OsdsSelect
value={selectedId}
data-testid="add-filter_select_idColumn"
onOdsValueChange={(event) =>
setSelectedId(event.detail.value as string)
}
>
{columns.map(({ id, label }) => (
<OsdsSelectOption key={id} value={id}>
{label}
</OsdsSelectOption>
))}
</OsdsSelect>
</OsdsFormField>
<OsdsFormField className="mt-2">
<div slot="label">
<OsdsText
level={ODS_TEXT_LEVEL.heading}
color={ODS_THEME_COLOR_INTENT.text}
>
{t('common_criteria_adder_operator_label')}
</OsdsText>
</div>
<OsdsSelect
value={selectedComparator}
onOdsValueChange={(event) => {
setSelectedComparator(event.detail.value as FilterComparator);
}}
>
{selectedColumn?.comparators?.map((comp) => (
<OsdsSelectOption key={comp} value={comp}>
{t(`${'common_criteria_adder_operator_'}${comp}`)}
</OsdsSelectOption>
))}
</OsdsSelect>
</OsdsFormField>
<OsdsFormField className="mt-2">
<div slot="label">
<OsdsText
level={ODS_TEXT_LEVEL.heading}
color={ODS_THEME_COLOR_INTENT.text}
>
{t('common_criteria_adder_value_label')}
</OsdsText>
</div>

<OsdsInput
className="border"
type={ODS_INPUT_TYPE.text}
color={ODS_THEME_COLOR_INTENT.primary}
value={value}
data-testid="filter-add_value-input"
onOdsValueChange={(e) => setValue(`${e.detail.value}`)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
submitAddFilter();
}
}}
/>
</OsdsFormField>
<OsdsButton
className="mt-4"
color={ODS_THEME_COLOR_INTENT.primary}
size={ODS_BUTTON_SIZE.sm}
disabled={value ? undefined : true}
onClick={submitAddFilter}
data-testid="filter-add_submit"
>
{t('common_criteria_adder_submit_label')}
</OsdsButton>
</>
);
}
Loading

0 comments on commit 07959d4

Please sign in to comment.