Skip to content

Commit

Permalink
feat(pci-object-storage): add import policy nodal
Browse files Browse the repository at this point in the history
ref: DTCORE-2879
Signed-off-by: Yoann Fievez <[email protected]>
  • Loading branch information
kqesar committed Nov 29, 2024
1 parent 71a49e2 commit 5f36c38
Show file tree
Hide file tree
Showing 19 changed files with 493 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/manager/apps/pci-object-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@tanstack/react-query": "^5.51.21",
"@tanstack/react-table": "^8.20.1",
"element-internals-polyfill": "^1.3.12",
"file-saver": "^2.0.5",
"i18next": "^23.8.2",
"i18next-http-backend": "^2.5.2",
"react": "^18.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Datei importieren",
"pci_projects_project_storages_containers_users_import_submit_label": "Importieren",
"pci_projects_project_storages_containers_users_import_add_files_label": "Dateien importieren",
"pci_projects_project_storages_containers_users_import_description": "Sie können eine JSON-Datei importieren und damit die Rechte des Nutzers verwalten.",
"pci_projects_project_storages_containers_users_import_success": "Die JSON-Datei für den Nutzer {{ username }} wurde vollständig importiert.",
"pci_projects_project_storages_containers_users_import_error": "Die JSON-Datei für den Nutzer {{ username }} wurde nicht korrekt importiert: {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Beim Download der Datei ist ein Fehler aufgetreten."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Import file",
"pci_projects_project_storages_containers_users_import_submit_label": "Import",
"pci_projects_project_storages_containers_users_import_add_files_label": "Import files",
"pci_projects_project_storages_containers_users_import_description": "You can import a JSON file to manage your user's rights.",
"pci_projects_project_storages_containers_users_import_success": "The JSON file linked to the user {{username}} has been imported.",
"pci_projects_project_storages_containers_users_import_error": "The JSON file linked to the user {{username}} was not imported correctly: {{message}}",
"pci_projects_project_storages_containers_users_import_read_page_error": "An error has occurred attempting to download the file."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Importar un archivo",
"pci_projects_project_storages_containers_users_import_submit_label": "Importar",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importar archivos",
"pci_projects_project_storages_containers_users_import_description": "Puede importar un archivo JSON para administrar los permisos de su usuario.",
"pci_projects_project_storages_containers_users_import_success": "El archivo JSON asociado al usuario {{ username }} se ha importado correctamente.",
"pci_projects_project_storages_containers_users_import_error": "El archivo JSON asociado al usuario {{ username }} no se ha importado correctamente: {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Se ha producido un error al intentar descargar el archivo."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"pci_projects_project_storages_containers_users_import_title": "Importer un fichier",
"pci_projects_project_storages_containers_users_import_submit_label": "Importer",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importer des fichiers",
"pci_projects_project_storages_containers_users_import_description": "Vous pouvez importer un fichier JSON pour gérer les droits de votre utilisateur.",

"pci_projects_project_storages_containers_users_import_success": "Le fichier JSON lié à l’utilisateur {{ username }} a été importé avec succès.",
"pci_projects_project_storages_containers_users_import_error": "Le fichier JSON lié à l’utilisateur {{ username }} n’a pas été correctement importé : {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Une erreur s'est produite lors de la tentative de téléchargement du fichier."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"pci_projects_project_storages_containers_users_import_title": "Importer un fichier",
"pci_projects_project_storages_containers_users_import_submit_label": "Importer",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importer des fichiers",
"pci_projects_project_storages_containers_users_import_description": "Vous pouvez importer un fichier JSON pour gérer les droits de votre utilisateur.",

"pci_projects_project_storages_containers_users_import_success": "Le fichier JSON lié à l’utilisateur {{ username }} a été importé avec succès.",
"pci_projects_project_storages_containers_users_import_error": "Le fichier JSON lié à l’utilisateur {{ username }} n’a pas été correctement importé : {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Une erreur s'est produite lors de la tentative de téléchargement du fichier."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Importa un file",
"pci_projects_project_storages_containers_users_import_submit_label": "Importa",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importa file",
"pci_projects_project_storages_containers_users_import_description": "Puoi importare un file JSON per gestire i diritti di un utente.",
"pci_projects_project_storages_containers_users_import_success": "Il file JSON associato all'utente {{ username }} è stato importato correttamente.",
"pci_projects_project_storages_containers_users_import_error": "Il file JSON associato all'utente {{ username }} non è stato importato correttamente: {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Si è verificato un errore durante il tentativo di scaricare il file."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Zaimportuj plik",
"pci_projects_project_storages_containers_users_import_submit_label": "Importuj",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importuj pliki",
"pci_projects_project_storages_containers_users_import_description": "Możesz zaimportować plik JSON, aby zarządzać uprawnieniami użytkownika.",
"pci_projects_project_storages_containers_users_import_success": "Plik JSON powiązany z użytkownikiem {{username}} został zaimportowany.",
"pci_projects_project_storages_containers_users_import_error": "Plik JSON powiązany z użytkownikiem {{username}} nie został poprawnie zaimportowany: {{message}}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Wystąpił błąd podczas próby pobrania pliku."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pci_projects_project_storages_containers_users_import_title": "Importar ficheiro",
"pci_projects_project_storages_containers_users_import_submit_label": "Importar",
"pci_projects_project_storages_containers_users_import_add_files_label": "Importar ficheiros",
"pci_projects_project_storages_containers_users_import_description": "Pode importar um ficheiro JSON para gerir os direitos do seu utilizador.",
"pci_projects_project_storages_containers_users_import_success": "O ficheiro JSON associado ao utilizador {{ username }} foi importado com êxito.",
"pci_projects_project_storages_containers_users_import_error": "O ficheiro JSON associado ao utilizador {{ username }} não foi corretamente importado: {{ message }}",
"pci_projects_project_storages_containers_users_import_read_page_error": "Ocorreu um erro durante a tentativa de carregamento do ficheiro."
}
14 changes: 14 additions & 0 deletions packages/manager/apps/pci-object-storage/src/api/data/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,17 @@ export const deleteUser = async (
`/cloud/project/${projectId}/user/${userId}/s3Credentials/${accessKey}`,
);
};

export const importUserPolicy = async (
projectId: string,
userId: string,
policy: string,
) => {
const { data } = await v6.post(
`/cloud/project/${projectId}/user/${userId}/policy`,
{
policy,
},
);
return data;
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useMutation, useQueries, useQuery } from '@tanstack/react-query';
import { ColumnSort, PaginationState } from '@ovh-ux/manager-react-components';
import { applyFilters, Filter } from '@ovh-ux/manager-core-api';
import { useMemo } from 'react';
import { paginateResults, sortResults } from '@/helpers';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { isJson, paginateResults, sortResults } from '@/helpers';
import {
deleteUser,
getAllUsers,
getS3Credentials,
importUserPolicy,
TS3Credentials,
TUser,
} from '@/api/data/user';
Expand Down Expand Up @@ -105,3 +107,59 @@ export const useDeleteUser = ({
...mutation,
};
};

type ImportPolicyProps = {
projectId: string;
userId: string;
files: File[];
onError: (cause: Error) => void;
onSuccess: () => void;
};

const readFileAsJSON = (file: File, t): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
if (isJson(reader.result.toString())) {
resolve(reader.result.toString());
} else {
reject(
new Error(
t(
'pci_projects_project_storages_containers_users_import_read_page_error',
),
),
);
}
};

reader.readAsText(file);
});
};

export const useImportPolicy = ({
projectId,
userId,
files,
onError,
onSuccess,
}: ImportPolicyProps) => {
const [isPending, setIsPending] = useState(false);
const { t } = useTranslation('objects/users/import-policy');
const importPolicy = async () => {
try {
setIsPending(true);
const policy = await readFileAsJSON(files[0], t);
importUserPolicy(projectId, userId, policy);
onSuccess();
} catch (e) {
onError(e as Error);
} finally {
setIsPending(false);
}
};
return {
isPending,
importPolicy,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export default function ActionsComponent({
);

const deleteHref = useHref(`./${user.id}/delete`);
const importHref = useHref(`./import-policy?userId=${user.id}`);
const items = [
{
id: 0,
label: t('pci_projects_project_storages_containers_users_import_json'),
href: importHref,
},
{
id: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useBytes } from '@ovh-ux/manager-pci-common';
import {
ODS_THEME_COLOR_INTENT,
ODS_THEME_SIZE,
ODS_THEME_TYPOGRAPHY_LEVEL,
} from '@ovhcloud/ods-common-theming';
import {
ODS_BUTTON_SIZE,
ODS_BUTTON_VARIANT,
ODS_ICON_NAME,
ODS_ICON_SIZE,
ODS_TEXT_SIZE,
} from '@ovhcloud/ods-components';
import { OsdsButton, OsdsIcon, OsdsText } from '@ovhcloud/ods-components/react';
import { ChangeEvent, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

type FileInputComponentProps = {
onFilesSelected: (files: File[]) => void;
};

export default function FileInputComponent({
onFilesSelected,
}: Readonly<FileInputComponentProps>) {
const { t } = useTranslation('pci-common');
const { formatBytes } = useBytes();
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const fileInputRef = useRef<HTMLInputElement | null>(null);

// Gérer la sélection des fichiers
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const newFiles = Array.from(event.target.files);

const updatedFiles = [...selectedFiles, ...newFiles];
setSelectedFiles(updatedFiles);
onFilesSelected(updatedFiles); // Met à jour la liste des fichiers dans le parent
};

// Supprimer un fichier de la liste
const handleRemoveFile = (index: number) => {
const updatedFiles = selectedFiles.filter((_, i) => i !== index);
setSelectedFiles(updatedFiles);
onFilesSelected(updatedFiles); // Met à jour la liste des fichiers dans le parent
};

// Ouvrir la boîte de dialogue de fichiers
const handleOpenFileDialog = () => {
fileInputRef.current.click();
};

return (
<div>
<OsdsButton
size={ODS_BUTTON_SIZE.sm}
onClick={handleOpenFileDialog}
color={ODS_THEME_COLOR_INTENT.primary}
class="w-fit"
>
<OsdsIcon
name={ODS_ICON_NAME.FOLDER}
size={ODS_ICON_SIZE.xxs}
className="bg-white mr-6 align-middle"
/>
{t('common_file_filesSelector')}
</OsdsButton>

<input
type="file"
multiple
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
/>

{selectedFiles.length > 0 && (
<div className="mt-4">
<OsdsText
level={ODS_THEME_TYPOGRAPHY_LEVEL.caption}
size={ODS_TEXT_SIZE._100}
color={ODS_THEME_COLOR_INTENT.text}
>
{t('common_file_attachmentsHeading')}
</OsdsText>
{selectedFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between bg-[var(--ods-color-primary-075)] border-color-[var(--ods-color-primary-700)] p-2 pl-4 rounded mb-2"
>
<div>
<OsdsIcon
name={ODS_ICON_NAME.FILE}
size={ODS_ICON_SIZE.xs}
color={ODS_THEME_COLOR_INTENT.primary}
className="mr-2 align-middle"
/>
<OsdsText
color={ODS_THEME_COLOR_INTENT.text}
level={ODS_THEME_TYPOGRAPHY_LEVEL.caption}
size={ODS_TEXT_SIZE._100}
>
{file.name} ({formatBytes(file.size)})
</OsdsText>
</div>
<OsdsButton
onClick={() => handleRemoveFile(index)}
variant={ODS_BUTTON_VARIANT.ghost}
size={ODS_BUTTON_SIZE.sm}
className="text-red-600 hover:text-red-800"
>
<OsdsIcon
name={ODS_ICON_NAME.CLOSE}
size={ODS_ICON_SIZE.xxs}
color={ODS_THEME_COLOR_INTENT.primary}
/>
</OsdsButton>
</div>
))}
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import LabelComponent from './Label.component';

describe('LabelComponent', () => {
it('renders the label text', () => {
const { getByText } = render(<LabelComponent text="Test Label" />);
expect(getByText('Test Label')).toBeInTheDocument();
});

it('renders the help text when provided', () => {
const { getByText } = render(
<LabelComponent text="Test Label" helpText="Help Text" />,
);
expect(getByText('Help Text')).toBeInTheDocument();
});

it('does not render the help text when not provided', () => {
const { queryByText } = render(<LabelComponent text="Test Label" />);
expect(queryByText('Help Text')).not.toBeInTheDocument();
});

it('applies error color when hasError is true', () => {
const { getByText } = render(<LabelComponent text="Test Label" hasError />);
const label = getByText('Test Label');
expect(label).toHaveAttribute('color', 'error');
});

it('applies custom className when provided', () => {
const { getByText } = render(
<LabelComponent text="Test Label" className="custom-class" />,
);

const { parentElement } = getByText('Test Label');
expect(parentElement).toHaveClass('custom-class');
});
});
Loading

0 comments on commit 5f36c38

Please sign in to comment.