From 312dd28cb12e5dcf86f9eff1288c71d5a79fc8b1 Mon Sep 17 00:00:00 2001 From: Peter Muriuki Date: Mon, 15 Jul 2024 15:53:58 +0300 Subject: [PATCH] Fix lint issues --- .../fhir-import/src/components/statusTag.tsx | 44 +-- packages/fhir-import/src/constants.ts | 33 +- .../src/containers/ImportDetailView/index.tsx | 73 ++-- .../ImportDetailView/tests/index.test.tsx | 4 +- .../src/containers/ImportListView/index.tsx | 44 ++- .../ImportListView/tests/fixtures.tsx | 27 +- .../ImportListView/tests/index.test.tsx | 21 +- .../src/containers/StartImportView/form.tsx | 332 ++++++++++-------- .../StartImportView/formInstructions.tsx | 117 ++++-- .../src/containers/StartImportView/index.tsx | 37 +- .../StartImportView/tests/form.test.tsx | 156 ++++---- .../tests/formInstruction.test.tsx | 96 ++--- .../StartImportView/tests/index.test.tsx | 42 +-- packages/fhir-import/src/helpers/utils.tsx | 20 +- packages/fhir-import/src/index.tsx | 9 +- packages/rbac/src/adapters/keycloakAdapter.ts | 7 +- packages/rbac/src/constants.ts | 15 +- packages/rbac/src/helpers/utils.ts | 6 +- 18 files changed, 579 insertions(+), 504 deletions(-) diff --git a/packages/fhir-import/src/components/statusTag.tsx b/packages/fhir-import/src/components/statusTag.tsx index f9bb3d759..5a697d61d 100644 --- a/packages/fhir-import/src/components/statusTag.tsx +++ b/packages/fhir-import/src/components/statusTag.tsx @@ -1,32 +1,26 @@ import React from 'react'; import { Tag } from 'antd'; +import { JobStatus } from '../helpers/utils'; export const ImportStatusTag = ({ statusString }: { statusString: JobStatus }) => { - const tagStatusColor = getStatusColor(statusString) - return ( - {statusString} - ) -} - -type JobStatus = - | 'completed' - | 'waiting' - | 'active' - | 'delayed' - | 'failed' - | 'paused'; + const tagStatusColor = getStatusColor(statusString); + return {statusString}; +}; +/** + * @param statusString - get tag status color for a job status + */ export function getStatusColor(statusString: JobStatus) { - switch (statusString) { - case "completed": - return "success" - case "active": - return "processing" - case "failed": - return "error" - case "paused": - return "warning" - default: - return "default" - } + switch (statusString) { + case 'completed': + return 'success'; + case 'active': + return 'processing'; + case 'failed': + return 'error'; + case 'paused': + return 'warning'; + default: + return 'default'; + } } diff --git a/packages/fhir-import/src/constants.ts b/packages/fhir-import/src/constants.ts index 2e7ed6d69..2e1263139 100644 --- a/packages/fhir-import/src/constants.ts +++ b/packages/fhir-import/src/constants.ts @@ -1,19 +1,18 @@ -export const DATA_IMPORT_LIST_URL = "/import" -export const DATA_IMPORT_DETAIL_URL = "/importDetail" -export const DATA_IMPORT_CREATE_URL = "/importCreate" +export const DATA_IMPORT_LIST_URL = '/import'; +export const DATA_IMPORT_DETAIL_URL = '/importDetail'; +export const DATA_IMPORT_CREATE_URL = '/importCreate'; -export const IMPORT_ENDPOINT = "/$import" -export const IMPORT_DOMAIN_URI="" -export const IMPORT_TEMPLATE_ENDPOINT = `${IMPORT_ENDPOINT}/templates` +export const IMPORT_ENDPOINT = '/$import'; +export const IMPORT_DOMAIN_URI = ''; +export const IMPORT_TEMPLATE_ENDPOINT = `${IMPORT_ENDPOINT}/templates`; - -export const dataImportRQueryKey = "dataImport" -export const locations = "locations" as const -export const users = "users" as const -export const organizations = "organizations" as const -export const careteams = "careteams" as const -export const inventories = "inventories" as const -export const orgToLocationAssignment = "orgToLocationAssignment" as const -export const userToOrganizationAssignment = "userToOrganizationAssignment" as const -export const products = "products" as const -export const productImages = "productImages" as const +export const dataImportRQueryKey = 'dataImport'; +export const locations = 'locations' as const; +export const users = 'users' as const; +export const organizations = 'organizations' as const; +export const careteams = 'careteams' as const; +export const inventories = 'inventories' as const; +export const orgToLocationAssignment = 'orgToLocationAssignment' as const; +export const userToOrganizationAssignment = 'userToOrganizationAssignment' as const; +export const products = 'products' as const; +export const productImages = 'productImages' as const; diff --git a/packages/fhir-import/src/containers/ImportDetailView/index.tsx b/packages/fhir-import/src/containers/ImportDetailView/index.tsx index 27c5b2143..89692f968 100644 --- a/packages/fhir-import/src/containers/ImportDetailView/index.tsx +++ b/packages/fhir-import/src/containers/ImportDetailView/index.tsx @@ -1,17 +1,21 @@ import React from 'react'; import { Spin, Tabs } from 'antd'; import { useQuery } from 'react-query'; -import { - BrokenPage, -} from '@opensrp/react-utils'; +import { BrokenPage } from '@opensrp/react-utils'; import { dataImportRQueryKey, IMPORT_DOMAIN_URI } from '../../constants'; import { useTranslation } from '../../mls'; -import { OpenSRPService, BodyLayout, ResourceDetails, Resource404, KeyValuesDescriptions } from '@opensrp/react-utils'; +import { + OpenSRPService, + BodyLayout, + ResourceDetails, + Resource404, + KeyValuesDescriptions, +} from '@opensrp/react-utils'; import { Helmet } from 'react-helmet'; import { useParams } from 'react-router'; -import { formatTimestamp } from '../../helpers/utils' -import { getStatusColor } from '../../components/statusTag' -import "./index.css"; +import { formatTimestamp, WorkflowDescription } from '../../helpers/utils'; +import { getStatusColor } from '../../components/statusTag'; +import './index.css'; /** typings for the view details component */ export interface RouteComponentProps { @@ -21,25 +25,26 @@ export interface RouteComponentProps { /** * Details view for a single workflow during bulk uploads. * - * @param props - detail view component props + * @param _ - detail view component props */ export const ImportDetailViewDetails = (_: RouteComponentProps) => { const params = useParams(); const workflowId = params.workflowId; const { t } = useTranslation(); - const { data, isLoading, error } = useQuery( - [dataImportRQueryKey, workflowId], () => { - const service = new OpenSRPService(`/$import`, IMPORT_DOMAIN_URI) - return service.read(workflowId).then(res => { - return res - }) - }, { - enabled: !!workflowId, - } + const { data, isLoading, error } = useQuery( + [dataImportRQueryKey, workflowId], + () => { + const service = new OpenSRPService(`/$import`, IMPORT_DOMAIN_URI); + return service.read(workflowId).then((res) => { + return res; + }); + }, + { + enabled: !!workflowId, + } ); - if (isLoading) { return ; } @@ -60,7 +65,6 @@ export const ImportDetailViewDetails = (_: RouteComponentProps) => { }, }; - const dateCreatedKeyPairing = { [t('Date Created')]: formatTimestamp(data.dateCreated), }; @@ -73,7 +77,7 @@ export const ImportDetailViewDetails = (_: RouteComponentProps) => { [t('Workflow type')]: data.workflowType, [t('Date Started')]: formatTimestamp(data.dateStarted), [t('Date Ended')]: formatTimestamp(data.dateEnded), - [t('Author')]: data.author + [t('Author')]: data.author, }; return ( @@ -81,14 +85,14 @@ export const ImportDetailViewDetails = (_: RouteComponentProps) => { {pageTitle} -
+
} /> @@ -96,19 +100,22 @@ export const ImportDetailViewDetails = (_: RouteComponentProps) => { data-testid="details-tab" style={{ width: '100%' }} size={'small'} - items={[{ - label: t('Log Output'), - key: "logOutput", - children:
-
-                {data.statusReason?.stdout}
-                {data.statusReason?.stderr}
-              
-
, - }]} + items={[ + { + label: t('Log Output'), + key: 'logOutput', + children: ( +
+
+                    {data.statusReason?.stdout}
+                    {data.statusReason?.stderr}
+                  
+
+ ), + }, + ]} />
); }; - diff --git a/packages/fhir-import/src/containers/ImportDetailView/tests/index.test.tsx b/packages/fhir-import/src/containers/ImportDetailView/tests/index.test.tsx index 9d22222a8..0b764a692 100644 --- a/packages/fhir-import/src/containers/ImportDetailView/tests/index.test.tsx +++ b/packages/fhir-import/src/containers/ImportDetailView/tests/index.test.tsx @@ -106,7 +106,9 @@ describe('Care Teams list view', () => { `); - expect(document.querySelector(".view-details-container")!.textContent).toMatchSnapshot("innerDetailsText"); + expect( + (document.querySelector('.view-details-container') as HTMLElement).textContent + ).toMatchSnapshot('innerDetailsText'); expect(nock.pendingMocks()).toEqual([]); }); diff --git a/packages/fhir-import/src/containers/ImportListView/index.tsx b/packages/fhir-import/src/containers/ImportListView/index.tsx index c2410be25..ce6346c2c 100644 --- a/packages/fhir-import/src/containers/ImportListView/index.tsx +++ b/packages/fhir-import/src/containers/ImportListView/index.tsx @@ -5,22 +5,18 @@ import { BodyLayout, OpenSRPService } from '@opensrp/react-utils'; import { CloudUploadOutlined } from '@ant-design/icons'; import { RouteComponentProps } from 'react-router'; import { useHistory, Link } from 'react-router-dom'; -import { - BrokenPage, - TableLayout, -} from '@opensrp/react-utils'; +import { BrokenPage, TableLayout } from '@opensrp/react-utils'; import { DATA_IMPORT_DETAIL_URL, DATA_IMPORT_CREATE_URL, dataImportRQueryKey, - IMPORT_DOMAIN_URI + IMPORT_DOMAIN_URI, } from '../../constants'; import { useTranslation } from '../../mls'; import { RbacCheck } from '@opensrp/rbac'; import { useQuery } from 'react-query'; -import { WorkflowDescription, formatTimestamp } from '../../helpers/utils'; -import { ImportStatusTag } from '../../components/statusTag' - +import { JobStatus, WorkflowDescription, formatTimestamp } from '../../helpers/utils'; +import { ImportStatusTag } from '../../components/statusTag'; // route params for care team pages interface RouteParams { @@ -37,17 +33,16 @@ export const DataImportList = () => { const history = useHistory(); const { data, isFetching, isLoading, error } = useQuery(dataImportRQueryKey, () => { - const service = new OpenSRPService("/$import", IMPORT_DOMAIN_URI) - return service.list().then(res => { - return res - }) - }) + const service = new OpenSRPService('/$import', IMPORT_DOMAIN_URI); + return service.list().then((res) => { + return res; + }); + }); if (error && !data) { return ; } - const columns = [ { title: t('Workflow Id'), @@ -64,20 +59,20 @@ export const DataImportList = () => { { title: t('status'), dataIndex: 'status' as const, - render: (_: any) => { - return - } + render: (_: JobStatus) => { + return ; + }, }, { title: t('Date created'), dataIndex: 'dateCreated' as const, defaultSortOrder: 'descend' as const, sortDirections: ['ascend' as const, 'descend' as const], - sorter: (a: any, b: any) => { - const diff = a.dateCreated - b.dateCreated - return diff === 0 ? 0 : diff > 0 ? 1 : -1 + sorter: (a: WorkflowDescription, b: WorkflowDescription) => { + const diff = a.dateCreated - b.dateCreated; + return diff === 0 ? 0 : diff > 0 ? 1 : -1; }, - render: (_: any) => formatTimestamp(_), + render: (_: number) => formatTimestamp(_), }, { title: t('Actions'), @@ -88,7 +83,10 @@ export const DataImportList = () => { <> - + {t('View')} @@ -99,7 +97,7 @@ export const DataImportList = () => { ]; const tableProps = { - datasource: (data ?? []), + datasource: data ?? [], columns, loading: isFetching || isLoading, }; diff --git a/packages/fhir-import/src/containers/ImportListView/tests/fixtures.tsx b/packages/fhir-import/src/containers/ImportListView/tests/fixtures.tsx index 515e2992c..10db1e0e7 100644 --- a/packages/fhir-import/src/containers/ImportListView/tests/fixtures.tsx +++ b/packages/fhir-import/src/containers/ImportListView/tests/fixtures.tsx @@ -1,13 +1,16 @@ -export const workflows = [ { - "workflowId": "26aae779-0e6f-482d-82c3-a0fad1fd3689_orgToLocationAssignment", - "status": "completed", - "workflowType": "orgToLocationAssignment", - "dateCreated": 1720096011433, - "dateStarted": 1720096011435, - "dateEnded": 1720096013898, - "statusReason": { - "stdout": "Progress::Reading csv \n", - "stderr": "Start time: 15:26:53\nStarting csv import...\nReading csv file\nReturning records from csv file\nUnsupported request!\n{ \"final-response\": }\nEnd time: 15:26:53\nTotal time: 0.002815 seconds\n" +export const workflows = [ + { + workflowId: '26aae779-0e6f-482d-82c3-a0fad1fd3689_orgToLocationAssignment', + status: 'completed', + workflowType: 'orgToLocationAssignment', + dateCreated: 1720096011433, + dateStarted: 1720096011435, + dateEnded: 1720096013898, + statusReason: { + stdout: 'Progress::Reading csv \n', + stderr: + 'Start time: 15:26:53\nStarting csv import...\nReading csv file\nReturning records from csv file\nUnsupported request!\n{ "final-response": }\nEnd time: 15:26:53\nTotal time: 0.002815 seconds\n', + }, + filename: 'organizations_locations.csv', }, - "filename": "organizations_locations.csv" -}] +]; diff --git a/packages/fhir-import/src/containers/ImportListView/tests/index.test.tsx b/packages/fhir-import/src/containers/ImportListView/tests/index.test.tsx index 03aaf1725..e1726a98f 100644 --- a/packages/fhir-import/src/containers/ImportListView/tests/index.test.tsx +++ b/packages/fhir-import/src/containers/ImportListView/tests/index.test.tsx @@ -17,13 +17,13 @@ import { superUserRole } from '@opensrp/react-utils'; const fetch = require('node-fetch'); global.fetch = fetch; -jest.mock("../../../constants", () => { - return ({ +jest.mock('../../../constants', () => { + return { __esModule: true, ...Object.assign({}, jest.requireActual('../../../constants')), - IMPORT_DOMAIN_URI: "http://localhost" - }); -}) + IMPORT_DOMAIN_URI: 'http://localhost', + }; +}); const { QueryClient, QueryClientProvider } = reactQuery; @@ -41,7 +41,6 @@ jest.mock('@opensrp/notifications', () => ({ ...Object.assign({}, jest.requireActual('@opensrp/notifications')), })); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const AppWrapper = (props: any) => { return ( @@ -90,10 +89,7 @@ describe('Care Teams list view', () => { }); it('renders correctly', async () => { - nock(constants.IMPORT_DOMAIN_URI) - .get("/$import") - .reply(200, workflows) - .persist(); + nock(constants.IMPORT_DOMAIN_URI).get('/$import').reply(200, workflows).persist(); const history = createMemoryHistory(); history.push(constants.DATA_IMPORT_LIST_URL); @@ -105,7 +101,7 @@ describe('Care Teams list view', () => { ); await waitForElementToBeRemoved(document.querySelector('.ant-spin')); - expect(nock.pendingMocks()).toEqual([]) + expect(nock.pendingMocks()).toEqual([]); expect(document.querySelector('title')).toMatchInlineSnapshot(` @@ -119,7 +115,6 @@ describe('Care Teams list view', () => { }); }); - expect(nock.pendingMocks()).toEqual([]) - + expect(nock.pendingMocks()).toEqual([]); }); }); diff --git a/packages/fhir-import/src/containers/StartImportView/form.tsx b/packages/fhir-import/src/containers/StartImportView/form.tsx index 6d22ddc12..f89865bd8 100644 --- a/packages/fhir-import/src/containers/StartImportView/form.tsx +++ b/packages/fhir-import/src/containers/StartImportView/form.tsx @@ -1,33 +1,46 @@ import React from 'react'; import { Button, Form, Typography, UploadFile, Upload, Space } from 'antd'; -import UploadIcon from "@2fd/ant-design-icons/lib/Upload"; -import UploadOutlined from "@2fd/ant-design-icons/lib/UploadOutline"; +import UploadIcon from '@2fd/ant-design-icons/lib/Upload'; +import UploadOutlined from '@2fd/ant-design-icons/lib/UploadOutline'; import { UploadChangeParam } from 'antd/es/upload'; import { useHistory } from 'react-router'; import { useMutation, useQueryClient } from 'react-query'; -import { OpenSRPService } from "@opensrp/react-utils"; -import { locations, users, organizations, careteams, inventories, orgToLocationAssignment, userToOrganizationAssignment, products, productImages, DATA_IMPORT_LIST_URL, IMPORT_DOMAIN_URI, dataImportRQueryKey } from '../../constants'; +import { OpenSRPService } from '@opensrp/react-utils'; +import { + locations, + users, + organizations, + careteams, + inventories, + orgToLocationAssignment, + userToOrganizationAssignment, + products, + productImages, + DATA_IMPORT_LIST_URL, + IMPORT_DOMAIN_URI, + dataImportRQueryKey, +} from '../../constants'; import { useTranslation } from '../../mls'; -import { sendErrorNotification, sendSuccessNotification } from '@opensrp/notifications'; +import { sendErrorNotification, sendInfoNotification, sendSuccessNotification } from '@opensrp/notifications'; import { HTTPMethod, getDefaultHeaders } from '@opensrp/server-service'; -import "./form.css"; +import './form.css'; const { Text, Title } = Typography; interface DataImportFormProps { - hidden?: string[] + hidden?: string[]; } interface FormFields { - [locations]: UploadFile[]; - [users]: UploadFile[]; - [organizations]: UploadFile[]; - [careteams]: UploadFile[]; - [inventories]: UploadFile[]; - [orgToLocationAssignment]: UploadFile[]; - [userToOrganizationAssignment]: UploadFile[]; - [products]: UploadFile[]; - [productImages]: UploadFile[]; + [locations]: UploadFile[]; + [users]: UploadFile[]; + [organizations]: UploadFile[]; + [careteams]: UploadFile[]; + [inventories]: UploadFile[]; + [orgToLocationAssignment]: UploadFile[]; + [userToOrganizationAssignment]: UploadFile[]; + [products]: UploadFile[]; + [productImages]: UploadFile[]; } /** @@ -38,148 +51,159 @@ interface FormFields { * @param method - the HTTP method * @param data - data to be used for payload */ -export function customFetchOptions<T = any>( - _: AbortSignal, - accessToken: string, - method: HTTPMethod, - data?: T +export function customFetchOptions<T>( + _: AbortSignal, + accessToken: string, + method: HTTPMethod, + data?: T ): RequestInit { - const headers = getDefaultHeaders(accessToken) - return { - headers: { authorization: headers.authorization as string }, - method, - ...(data ? { body: data as any } : {}), - }; + const headers = getDefaultHeaders(accessToken); + return { + headers: { authorization: headers.authorization as string }, + method, + ...(data ? { body: data as BodyInit } : {}), + }; } export const DataImportForm = (props: DataImportFormProps) => { - const { hidden } = props - const queryClient = useQueryClient(); - const history = useHistory(); - const { t } = useTranslation(); - const goTo = (url = '#') => history.push(url); - - const { mutate, isLoading } = useMutation( - async (values: FormFields) => { - const service = new OpenSRPService<any>("/$import", IMPORT_DOMAIN_URI, customFetchOptions) - const formData = new FormData() - - Object.entries(values).forEach(([key, value]) => { - - if (value) { - formData.append(key, value?.[0]?.originFileObj) - } - }) - - service.create(formData) - }, - { - onError: (err: Error) => { - sendErrorNotification(err.message); - }, - onSuccess: async () => { - sendSuccessNotification(t('Data import started successfully')); - queryClient.invalidateQueries(dataImportRQueryKey) - goTo(DATA_IMPORT_LIST_URL); - }, + const { hidden } = props; + const queryClient = useQueryClient(); + const history = useHistory(); + const { t } = useTranslation(); + const goTo = (url = '#') => history.push(url); + + const { mutate, isLoading } = useMutation( + async (values: FormFields) => { + const service = new OpenSRPService('/$import', IMPORT_DOMAIN_URI, customFetchOptions); + const formData = new FormData(); + + Object.entries(values).forEach(([key, value]) => { + if (value) { + formData.append(key, value?.[0]?.originFileObj); } - - ); - - - const formItems = [{ - formFieldName: users, - label: "Users", - UploadBtnText: "Attach users file" - }, { - formFieldName: locations, - label: "Locations", - UploadBtnText: "Attach locations file" - }, { - formFieldName: organizations, - label: "Organizations", - UploadBtnText: "Attach organizations file" - }, { - formFieldName: careteams, - label: "CareTeams", - UploadBtnText: "Attach careTeams file" - }, { - formFieldName: orgToLocationAssignment, - label: "Organization location assignment", - UploadBtnText: "Attach assignment file" - }, { - formFieldName: userToOrganizationAssignment, - label: "User organization assignment", - UploadBtnText: "Attach assignment file" - }, { - formFieldName: inventories, - label: "Inventory", - UploadBtnText: "Attach inventory file" - }] - - return <Space direction='vertical' style={{ width: "100%" }}> - <Title level={4}><UploadIcon />Select files to upload - Supported file formats CSV -
{ - mutate(values); - }} - > - { - formItems.map(item => { - const { formFieldName, label, UploadBtnText } = item - return - }) - } + }); + + return service.create(formData); + }, + { + onError: (err: Error) => { + sendErrorNotification(err.message); + }, + onSuccess: async () => { + sendSuccessNotification(t('Data import started successfully')); + queryClient.invalidateQueries(dataImportRQueryKey).catch( () => { + sendInfoNotification(t('Failed to refresh data, please refresh the page')) + }); + goTo(DATA_IMPORT_LIST_URL); + }, + } + ); + + const formItems = [ + { + formFieldName: users, + label: 'Users', + UploadBtnText: 'Attach users file', + }, + { + formFieldName: locations, + label: 'Locations', + UploadBtnText: 'Attach locations file', + }, + { + formFieldName: organizations, + label: 'Organizations', + UploadBtnText: 'Attach organizations file', + }, + { + formFieldName: careteams, + label: 'CareTeams', + UploadBtnText: 'Attach careTeams file', + }, + { + formFieldName: orgToLocationAssignment, + label: 'Organization location assignment', + UploadBtnText: 'Attach assignment file', + }, + { + formFieldName: userToOrganizationAssignment, + label: 'User organization assignment', + UploadBtnText: 'Attach assignment file', + }, + { + formFieldName: inventories, + label: 'Inventory', + UploadBtnText: 'Attach inventory file', + }, + ]; + + return ( + + + <UploadIcon /> + Select files to upload + + Supported file formats CSV + { + mutate(values); + }} + > + {formItems.map((item) => { + const { formFieldName, label, UploadBtnText } = item; + return ( - - - - - - - + ); + })} + + + + + + +
-} + ); +}; /** * extract file from an input event @@ -187,9 +211,9 @@ export const DataImportForm = (props: DataImportFormProps) => { * @param e - event after a file upload */ export const normalizeFileInputEvent = (e: UploadChangeParam) => { - if (Array.isArray(e)) { - return e; - } + if (Array.isArray(e)) { + return e; + } - return e.fileList; -}; \ No newline at end of file + return e.fileList; +}; diff --git a/packages/fhir-import/src/containers/StartImportView/formInstructions.tsx b/packages/fhir-import/src/containers/StartImportView/formInstructions.tsx index 0a935a656..2566d68a7 100644 --- a/packages/fhir-import/src/containers/StartImportView/formInstructions.tsx +++ b/packages/fhir-import/src/containers/StartImportView/formInstructions.tsx @@ -1,61 +1,104 @@ -import React from 'react' +import React from 'react'; import { Typography, Steps, Button, Space } from 'antd'; -import UploadIcon from '@2fd/ant-design-icons/lib/Upload' -import ArrowDownThick from '@2fd/ant-design-icons/lib/ArrowDownThick' +import UploadIcon from '@2fd/ant-design-icons/lib/Upload'; +import ArrowDownThick from '@2fd/ant-design-icons/lib/ArrowDownThick'; import { OpenSRPService, downloadFile, getFileNameFromCDHHeader } from '@opensrp/react-utils'; -import {IMPORT_DOMAIN_URI} from '../../constants'; +import { IMPORT_DOMAIN_URI } from '../../constants'; +import { useTranslation } from '../../mls'; +import { sendErrorNotification } from 'opensrp-notifications/dist/types'; const { Title, Text } = Typography; - export const ImporterFormInstructions = () => { - return
- - <UploadIcon />Step by step guide for bulk upload + const { t } = useTranslation(); + return ( +
+ + + <UploadIcon /> + {t('Step by step guide for bulk upload')} + - Follow these simple instructions to help you prepare, upload, and verify your data smoothly and efficiently + {t( + 'Follow these simple instructions to help you prepare, upload, and verify your data smoothly and efficiently' + )} - - + className="form-instructions-steps" + direction="vertical" + size="small" + items={[ + { title: InstructionStepOneTitle, description: InstructionStepOne }, + { title: InstructionStepTwoTitle, description: InstructionStepTwo }, + ]} + > +
-} + ); +}; -export const InstructionStepOneTitle = Prepare your data file -export const InstructionStepOne = -
    -
  1. Click the button below to download the bulk upload template file(s)

    -
  2. -
  3. Enter your data into the template file. Ensure all required fields are filled and follow the specified format(e.g. date format,)
  4. -
  5. Check for any data inconsistencies or errors (e.g. missing values, incorrect data types) before uploading
  6. + }) + .catch(() => + sendErrorNotification(t('An error occurred while fetching the csv templates')) + ); + }} + > + {t('Download Template')} + + + +
  7. + {t( + 'Enter your data into the template file. Ensure all required fields are filled and follow the specified format(e.g. date format)' + )} +
  8. +
  9. + {t( + 'Check for any data inconsistencies or errors (e.g. missing values, incorrect data types) before uploading' + )} +
+ ); +}; - -export const InstructionStepTwoTitle = Upload your data file -export const InstructionStepTwo = +export const InstructionStepTwoTitle = () => { + const { t } = useTranslation(); + return {t('Upload your data file')}; +}; +export const InstructionStepTwo = () => { + const { t } = useTranslation(); + return (
    -
  1. Click the "Attach" button to select your prepared data file.
  2. -
  3. Once the file or files are selected, click "Start Import" to begin the upload
  4. +
  5. {t('Click the "Attach" button to select your prepared data file.')}
  6. +
  7. {t('Once the file or files are selected, click "Start Import" to begin the upload')}
- + ); +}; diff --git a/packages/fhir-import/src/containers/StartImportView/index.tsx b/packages/fhir-import/src/containers/StartImportView/index.tsx index aca4512a4..620c79de9 100644 --- a/packages/fhir-import/src/containers/StartImportView/index.tsx +++ b/packages/fhir-import/src/containers/StartImportView/index.tsx @@ -1,30 +1,31 @@ -import React from 'react' +import React from 'react'; import { Helmet } from 'react-helmet'; import { ImporterFormInstructions } from './formInstructions'; import { BodyLayout } from '@opensrp/react-utils'; -import { Row, Col } from 'antd' +import { Row, Col } from 'antd'; import { DataImportForm } from './form'; - export const StartDataImport = () => { - const pageTitle = "Data imports" + const pageTitle = 'Data imports'; const headerProps = { pageHeaderProps: { title: pageTitle, onBack: undefined, }, }; - return <> - - - {pageTitle} - - - - - - - - - -} + return ( + <> + + + {pageTitle} + + + + + + + + + + ); +}; diff --git a/packages/fhir-import/src/containers/StartImportView/tests/form.test.tsx b/packages/fhir-import/src/containers/StartImportView/tests/form.test.tsx index 54b13f019..89515899b 100644 --- a/packages/fhir-import/src/containers/StartImportView/tests/form.test.tsx +++ b/packages/fhir-import/src/containers/StartImportView/tests/form.test.tsx @@ -13,109 +13,107 @@ import * as constants from '../../../constants'; const fetch = require('node-fetch'); global.fetch = fetch; -jest.mock("../../../constants", () => { - return ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('../../../constants')), - IMPORT_DOMAIN_URI: "http://localhost" - }); -}) +jest.mock('../../../constants', () => { + return { + __esModule: true, + ...Object.assign({}, jest.requireActual('../../../constants')), + IMPORT_DOMAIN_URI: 'http://localhost', + }; +}); jest.mock('@opensrp/notifications', () => ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('@opensrp/notifications')), + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/notifications')), })); - const { QueryClient, QueryClientProvider } = reactQuery; const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - cacheTime: 0, - }, + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, }, + }, }); - beforeAll(() => { - nock.disableNetConnect(); - store.dispatch( - authenticateUser( - true, - { - email: 'bob@example.com', - name: 'Bobbie', - username: 'RobertBaratheon', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } - ) - ); + nock.disableNetConnect(); + store.dispatch( + authenticateUser( + true, + { + email: 'bob@example.com', + name: 'Bobbie', + username: 'RobertBaratheon', + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } + ) + ); }); afterAll(() => { - nock.enableNetConnect(); + nock.enableNetConnect(); }); afterEach(() => { - jest.resetAllMocks(); - jest.clearAllMocks(); - jest.restoreAllMocks(); - cleanup(); + jest.resetAllMocks(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + cleanup(); }); const sampleCsv = new File([''], 'sample.csv', { type: 'text/csv' }); // TODO - form values is not correctly updated even if individual form items correctly // register a change event. -test.skip("creates an import submission correctly", async () => { - nock(constants.IMPORT_DOMAIN_URI) - .post("/$import", () => { - return true +// eslint-disable-next-line jest/no-disabled-tests +test.skip('creates an import submission correctly', async () => { + nock(constants.IMPORT_DOMAIN_URI) + .post('/$import', () => { + return true; }) .reply(200, []) .persist(); - const successNoticeMock = jest - .spyOn(notifications, 'sendSuccessNotification') - .mockImplementation(() => undefined); - - const errorNoticeMock = jest - .spyOn(notifications, 'sendErrorNotification') - .mockImplementation(() => undefined); - - render( - - - - ) - - const usersField = screen.getByLabelText('Users'); - userEvent.upload(usersField, sampleCsv); - const locationsField = screen.getByLabelText('Locations'); - userEvent.upload(locationsField, sampleCsv); - const organizationsField = screen.getByLabelText('Organizations'); - userEvent.upload(organizationsField, sampleCsv); - const careTeamsField = screen.getByLabelText('CareTeams'); - userEvent.upload(careTeamsField, sampleCsv); - const orgLocationAssignmentField = screen.getByLabelText('Organization location assignment'); - userEvent.upload(orgLocationAssignmentField, sampleCsv); - const userOrgAssignmentField = screen.getByLabelText('User organization assignment'); - userEvent.upload(userOrgAssignmentField, sampleCsv); - const inventoryField = screen.getByLabelText('Inventory'); - userEvent.upload(inventoryField, sampleCsv); - const productsField = screen.getByLabelText('Products'); - userEvent.upload(productsField, sampleCsv); - - fireEvent.click(screen.getByRole('button', { name: /save/i })); - - await waitFor(() => { - expect(errorNoticeMock).not.toHaveBeenCalled(); - expect(successNoticeMock.mock.calls).toEqual([['Commodity updated successfully']]); - }); - - expect(nock.isDone()).toBeTruthy(); -}) - + const successNoticeMock = jest + .spyOn(notifications, 'sendSuccessNotification') + .mockImplementation(() => undefined); + + const errorNoticeMock = jest + .spyOn(notifications, 'sendErrorNotification') + .mockImplementation(() => undefined); + + render( + + + + ); + + const usersField = screen.getByLabelText('Users'); + userEvent.upload(usersField, sampleCsv); + const locationsField = screen.getByLabelText('Locations'); + userEvent.upload(locationsField, sampleCsv); + const organizationsField = screen.getByLabelText('Organizations'); + userEvent.upload(organizationsField, sampleCsv); + const careTeamsField = screen.getByLabelText('CareTeams'); + userEvent.upload(careTeamsField, sampleCsv); + const orgLocationAssignmentField = screen.getByLabelText('Organization location assignment'); + userEvent.upload(orgLocationAssignmentField, sampleCsv); + const userOrgAssignmentField = screen.getByLabelText('User organization assignment'); + userEvent.upload(userOrgAssignmentField, sampleCsv); + const inventoryField = screen.getByLabelText('Inventory'); + userEvent.upload(inventoryField, sampleCsv); + const productsField = screen.getByLabelText('Products'); + userEvent.upload(productsField, sampleCsv); + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + + await waitFor(() => { + expect(errorNoticeMock).not.toHaveBeenCalled(); + expect(successNoticeMock.mock.calls).toEqual([['Commodity updated successfully']]); + }); + + expect(nock.isDone()).toBeTruthy(); +}); diff --git a/packages/fhir-import/src/containers/StartImportView/tests/formInstruction.test.tsx b/packages/fhir-import/src/containers/StartImportView/tests/formInstruction.test.tsx index 8291affba..2ac85ae2c 100644 --- a/packages/fhir-import/src/containers/StartImportView/tests/formInstruction.test.tsx +++ b/packages/fhir-import/src/containers/StartImportView/tests/formInstruction.test.tsx @@ -5,83 +5,85 @@ import nock from 'nock'; import { render, cleanup, screen, fireEvent, waitFor } from '@testing-library/react'; import { store } from '@opensrp/store'; import * as constants from '../../../constants'; -import * as reactUtils from '@opensrp/react-utils' +import * as reactUtils from '@opensrp/react-utils'; // eslint-disable-next-line @typescript-eslint/no-var-requires const fetch = require('node-fetch'); global.fetch = fetch; jest.mock('../../../constants', () => { - return { - __esModule: true, - ...Object.assign({}, jest.requireActual('../../../constants')), - IMPORT_DOMAIN_URI: 'http://localhost', - }; + return { + __esModule: true, + ...Object.assign({}, jest.requireActual('../../../constants')), + IMPORT_DOMAIN_URI: 'http://localhost', + }; }); jest.mock('@opensrp/notifications', () => ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('@opensrp/notifications')), + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/notifications')), })); - jest.mock('@opensrp/react-utils', () => ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('@opensrp/react-utils')), + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/react-utils')), })); beforeAll(() => { - nock.disableNetConnect(); - store.dispatch( - authenticateUser( - true, - { - email: 'bob@example.com', - name: 'Bobbie', - username: 'RobertBaratheon', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } - ) - ); + nock.disableNetConnect(); + store.dispatch( + authenticateUser( + true, + { + email: 'bob@example.com', + name: 'Bobbie', + username: 'RobertBaratheon', + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } + ) + ); }); afterAll(() => { - nock.enableNetConnect(); + nock.enableNetConnect(); }); afterEach(() => { - jest.resetAllMocks(); - jest.clearAllMocks(); - jest.restoreAllMocks(); - cleanup(); + jest.resetAllMocks(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + cleanup(); }); const templateZipped = new File([''], 'templates.zip', { type: 'application/zip' }); test('renders correctly', async () => { - nock(constants.IMPORT_DOMAIN_URI).get('/$import/templates').reply(200, templateZipped, { - "content-type": "application/zip" - }).persist(); + nock(constants.IMPORT_DOMAIN_URI) + .get('/$import/templates') + .reply(200, templateZipped, { + 'content-type': 'application/zip', + }) + .persist(); - const downloadFileSpy = jest.spyOn(reactUtils, "downloadFile").mockImplementation(() => { }) + const downloadFileSpy = jest.spyOn(reactUtils, 'downloadFile').mockImplementation(() => { + return; + }); - render( - - ); + render(); - const formInstruction = screen.getByTestId('form-instructions'); - expect(formInstruction.textContent).toMatchSnapshot("textContent"); + const formInstruction = screen.getByTestId('form-instructions'); + expect(formInstruction.textContent).toMatchSnapshot('textContent'); - // template download - const downloadTemplateBtn = screen.getByRole('button', { - name: /Download Template/i, - }); - fireEvent.click(downloadTemplateBtn); + // template download + const downloadTemplateBtn = screen.getByRole('button', { + name: /Download Template/i, + }); + fireEvent.click(downloadTemplateBtn); - await waitFor(() => { - expect(downloadFileSpy.mock.calls[0][1]).toEqual("import-template") - }); + await waitFor(() => { + expect(downloadFileSpy.mock.calls[0][1]).toEqual('import-template'); + }); - expect(nock.isDone()).toBeTruthy(); + expect(nock.isDone()).toBeTruthy(); }); diff --git a/packages/fhir-import/src/containers/StartImportView/tests/index.test.tsx b/packages/fhir-import/src/containers/StartImportView/tests/index.test.tsx index 7ff74d6a5..f0b12bd7e 100644 --- a/packages/fhir-import/src/containers/StartImportView/tests/index.test.tsx +++ b/packages/fhir-import/src/containers/StartImportView/tests/index.test.tsx @@ -4,42 +4,38 @@ import nock from 'nock'; import { render, cleanup, screen } from '@testing-library/react'; import * as reactQuery from 'react-query'; - const { QueryClient, QueryClientProvider } = reactQuery; const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - cacheTime: 0, - }, + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, }, + }, }); beforeAll(() => { - nock.disableNetConnect(); + nock.disableNetConnect(); }); afterAll(() => { - nock.enableNetConnect(); + nock.enableNetConnect(); }); afterEach(() => { - jest.resetAllMocks(); - jest.clearAllMocks(); - jest.restoreAllMocks(); - cleanup(); + jest.resetAllMocks(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + cleanup(); }); -test("renders correctly", async () => { - - render( - +test('renders correctly', async () => { + render( + + - ) - const formInstruction = screen.getByTestId('form-instructions'); - expect(formInstruction.textContent).toMatchSnapshot("textContent"); - - -}) - + ); + const formInstruction = screen.getByTestId('form-instructions'); + expect(formInstruction.textContent).toMatchSnapshot('textContent'); +}); diff --git a/packages/fhir-import/src/helpers/utils.tsx b/packages/fhir-import/src/helpers/utils.tsx index 6f41f2086..f81b48e70 100644 --- a/packages/fhir-import/src/helpers/utils.tsx +++ b/packages/fhir-import/src/helpers/utils.tsx @@ -1,14 +1,20 @@ export interface WorkflowDescription { - "workflowId": string; - "status": string; - "workflowType": string; - "dateStarted": number; - "dateEnded": number; - "dateCreated": number; - "statusReason"?: any; + workflowId: string; + status: JobStatus; + workflowType: string; + dateStarted: number; + dateEnded: number; + dateCreated: number; + statusReason?: {stdout: string, stderr: string}; filename: string; + author: string; } +export type JobStatus = 'completed' | 'waiting' | 'active' | 'delayed' | 'failed' | 'paused'; + +/** + * @param timestamp + */ export function formatTimestamp(timestamp: number) { return new Date(timestamp).toLocaleString(); } diff --git a/packages/fhir-import/src/index.tsx b/packages/fhir-import/src/index.tsx index 9aae78a47..04d9cd566 100644 --- a/packages/fhir-import/src/index.tsx +++ b/packages/fhir-import/src/index.tsx @@ -1,5 +1,4 @@ -export * from "./containers/ImportListView"; -export * from "./constants"; -export * from "./containers/StartImportView" -export * from "./containers/ImportDetailView" - +export * from './containers/ImportListView'; +export * from './constants'; +export * from './containers/StartImportView'; +export * from './containers/ImportDetailView'; diff --git a/packages/rbac/src/adapters/keycloakAdapter.ts b/packages/rbac/src/adapters/keycloakAdapter.ts index c1bf988cb..57065d8ec 100644 --- a/packages/rbac/src/adapters/keycloakAdapter.ts +++ b/packages/rbac/src/adapters/keycloakAdapter.ts @@ -1,4 +1,9 @@ -import { AuthZResource, FhirResource, KeycloakDefinedResource, KeycloakDefinedResources, Permit } from '../constants'; +import { + AuthZResource, + KeycloakDefinedResource, + KeycloakDefinedResources, + Permit, +} from '../constants'; import { RbacAdapter } from '../helpers/types'; import { UserRole } from '../roleDefinition'; diff --git a/packages/rbac/src/constants.ts b/packages/rbac/src/constants.ts index 995dd4fd3..66dc829ff 100644 --- a/packages/rbac/src/constants.ts +++ b/packages/rbac/src/constants.ts @@ -43,17 +43,16 @@ export const FhirResources = [ ] as const; export type FhirResource = typeof FhirResources[number]; -/** Roles for Situations where we have views that are not directly tied to any of the native fhir resources. - * These are custom and only relevant for the web. These should also be defined and parsed in a similar design as +/** + * Roles for Situations where we have views that are not directly tied to any of the native fhir resources. + * These are custom and only relevant for the web. These should also be defined and parsed in a similar design as * FhirResources */ -export const WebCustomResources = [ - 'WebDataImport' -] as const; -export type WebCustomResource = typeof WebCustomResources[number] +export const WebCustomResources = ['WebDataImport'] as const; +export type WebCustomResource = typeof WebCustomResources[number]; -export const KeycloakDefinedResources = [...FhirResources, ...WebCustomResources] as const -export type KeycloakDefinedResource = typeof KeycloakDefinedResources[number] +export const KeycloakDefinedResources = [...FhirResources, ...WebCustomResources] as const; +export type KeycloakDefinedResource = typeof KeycloakDefinedResources[number]; export type AuthZResource = IamResource | FhirResource | WebCustomResource; export type BinaryNumber = number; diff --git a/packages/rbac/src/helpers/utils.ts b/packages/rbac/src/helpers/utils.ts index fba685668..3c0e60ad3 100644 --- a/packages/rbac/src/helpers/utils.ts +++ b/packages/rbac/src/helpers/utils.ts @@ -23,7 +23,11 @@ export function makeArray(obj: T | T[]): T[] { } // TODO - this is not actually lowercased -const lowerCasedAuthZResources = [...IamResources, ...KeycloakDefinedResources, ...WebCustomResources].map( +const lowerCasedAuthZResources = [ + ...IamResources, + ...KeycloakDefinedResources, + ...WebCustomResources, +].map( (tag) => tag // tag.toLowerCase() ) as string[];