diff --git a/Dockerfile b/Dockerfile index 19d742ae0..e80f826c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,11 @@ FROM alpine/git AS sources -RUN git clone --depth=1 --branch=v2.0.1 https://github.com/onaio/express-server.git /usr/src/express-server +# TODO - update the tag here +RUN git clone --branch=bulk-upload-bull https://github.com/onaio/express-server.git /usr/src/express-server + +WORKDIR /usr/src/express-server + +RUN git checkout 378f2884 FROM node:16.18-alpine as build @@ -21,7 +26,6 @@ RUN chown -R node . USER node RUN yarn lerna run build - FROM node:16.18-alpine as nodejsbuild COPY --from=sources /usr/src/express-server /usr/src/express-server @@ -32,7 +36,9 @@ RUN yarn && yarn tsc && npm prune -production --legacy-peer-deps # Remove unused dependencies RUN rm -rf ./node_modules/typescript -FROM node:16.18-alpine as final +# TODO - change image to use one with python or install python here +FROM nikolaik/python-nodejs:python3.12-nodejs22-alpine as final + # Use tini for NodeJS application https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#handling-kernel-signals RUN apk add --no-cache tini curl @@ -52,21 +58,28 @@ WORKDIR /usr/src/web COPY --from=build /project/node_modules /usr/src/web/node_modules COPY --from=build /project/app/build /usr/src/web -RUN chown -R node /usr/src/web + +# RUN chown -R pn /usr/src/web WORKDIR /usr/src/app -COPY --from=nodejsbuild /usr/src/express-server/dist /usr/src/app +COPY --from=nodejsbuild /usr/src/express-server/build /usr/src/app COPY --from=nodejsbuild /usr/src/express-server/node_modules /usr/src/app/node_modules -RUN chown -R node /usr/src/app -USER node +RUN pip install -r /usr/src/app/fhir-tooling/requirements.txt + +# RUN chown -R pn /usr/src/app + +# USER pn ENV EXPRESS_REACT_BUILD_PATH /usr/src/web/ EXPOSE 3000 -CMD [ "/bin/sh", "-c", "/usr/local/bin/app.sh && node ." ] +CMD [ "/bin/sh", "-c", "/usr/local/bin/app.sh && node /usr/src/app/dist" ] + +# ENTRYPOINT [ "/bin/sh" ] +# ENTRYPOINT [ "/bin/sh", "-c", "/usr/local/bin/app.sh && node ." ] ENTRYPOINT ["/sbin/tini", "--"] diff --git a/app/src/App/fhir-apps.tsx b/app/src/App/fhir-apps.tsx index d7a8c1a60..3808acdb4 100644 --- a/app/src/App/fhir-apps.tsx +++ b/app/src/App/fhir-apps.tsx @@ -126,6 +126,7 @@ import { CloseFlag, URL_CLOSE_FLAGS } from '@opensrp/fhir-flag'; import { useTranslation } from '../mls'; import '@opensrp/user-management/dist/index.css'; import { APP_LOGIN_URL } from '../configs/dispatchConfig'; +import { DATA_IMPORT_CREATE_URL, ImportDetailViewDetails, DATA_IMPORT_DETAIL_URL, DATA_IMPORT_LIST_URL, DataImportList, StartDataImport } from '@opensrp/fhir-import'; /** Util function that renders Oauth2 callback components * @@ -203,6 +204,40 @@ const FHIRApps = () => { permissions={['iam_group.read']} component={UserGroupsList} /> + + + + }, + title: t('Data Imports'), + key: 'data-import', + enabled: true, + permissions: ['WebDataImport.read'], + url: DATA_IMPORT_LIST_URL, + isHomePageLink: true, + }, ]; return filterFalsyRoutes(routes, userRole); diff --git a/packages/fhir-import/README.md b/packages/fhir-import/README.md new file mode 100644 index 000000000..d5d275b50 --- /dev/null +++ b/packages/fhir-import/README.md @@ -0,0 +1,46 @@ +# Package + + + +## Installation + +```sh +yarn add @opensrp/template +``` + + + +## Usage + + + +### Code examples + + diff --git a/packages/fhir-import/index.d.ts b/packages/fhir-import/index.d.ts new file mode 100644 index 000000000..1fbea2e91 --- /dev/null +++ b/packages/fhir-import/index.d.ts @@ -0,0 +1,144 @@ +declare module '@2fd/ant-design-icons' { + import { ReactNode, SVGProps } from 'react'; + + export interface IconProps extends SVGProps { + title?: string; + size?: number | string; + color?: string; + } + + export interface IconComponent { + (props: IconProps): ReactNode; + } + + export const AccountBookOutlined: IconComponent; + export const AlertOutlined: IconComponent; + export const AliyunOutlined: IconComponent; + export const AmazonOutlined: IconComponent; + export const AndroidOutlined: IconComponent; + export const AppleOutlined: IconComponent; + export const AppstoreOutlined: IconComponent; + export const ArrowDownOutlined: IconComponent; + export const ArrowLeftOutlined: IconComponent; + export const ArrowRightOutlined: IconComponent; + export const ArrowUpOutlined: IconComponent; + export const AudioOutlined: IconComponent; + export const BackwardOutlined: IconComponent; + export const BankOutlined: IconComponent; + export const BarcodeOutlined: IconComponent; + export const BarsOutlined: IconComponent; + export const BellOutlined: IconComponent; + export const BookOutlined: IconComponent; + export const BoxPlotOutlined: IconComponent; + export const BugOutlined: IconComponent; + export const BuildOutlined: IconComponent; + export const BulbOutlined: IconComponent; + export const CalculatorOutlined: IconComponent; + export const CalendarOutlined: IconComponent; + export const CameraOutlined: IconComponent; + export const CarOutlined: IconComponent; + export const CarryOutOutlined: IconComponent; + export const CheckCircleOutlined: IconComponent; + export const CheckSquareOutlined: IconComponent; + export const ClockCircleOutlined: IconComponent; + export const CloudOutlined: IconComponent; + export const CodeOutlined: IconComponent; + export const CoffeeOutlined: IconComponent; + export const CompassOutlined: IconComponent; + export const ContactsOutlined: IconComponent; + export const ContainerOutlined: IconComponent; + export const ControlOutlined: IconComponent; + export const CreditCardOutlined: IconComponent; + export const CrownOutlined: IconComponent; + export const CustomerServiceOutlined: IconComponent; + export const DashboardOutlined: IconComponent; + export const DatabaseOutlined: IconComponent; + export const DeleteOutlined: IconComponent; + export const DesktopOutlined: IconComponent; + export const DislikeOutlined: IconComponent; + export const DollarOutlined: IconComponent; + export const DownloadOutlined: IconComponent; + export const EditOutlined: IconComponent; + export const EnvironmentOutlined: IconComponent; + export const ExclamationCircleOutlined: IconComponent; + export const ExperimentOutlined: IconComponent; + export const ExportOutlined: IconComponent; + export const EyeOutlined: IconComponent; + export const FileOutlined: IconComponent; + export const FilterOutlined: IconComponent; + export const FireOutlined: IconComponent; + export const FlagOutlined: IconComponent; + export const FolderOutlined: IconComponent; + export const FolderOpenOutlined: IconComponent; + export const FundProjectionScreenOutlined: IconComponent; + export const FunnelPlotOutlined: IconComponent; + export const GiftOutlined: IconComponent; + export const GlobalOutlined: IconComponent; + export const GoldOutlined: IconComponent; + export const HddOutlined: IconComponent; + export const HeartOutlined: IconComponent; + export const HomeOutlined: IconComponent; + export const HourglassOutlined: IconComponent; + export const IdcardOutlined: IconComponent; + export const InboxOutlined: IconComponent; + export const KeyOutlined: IconComponent; + export const LaptopOutlined: IconComponent; + export const LayoutOutlined: IconComponent; + export const LikeOutlined: IconComponent; + export const LineChartOutlined: IconComponent; + export const LockOutlined: IconComponent; + export const MailOutlined: IconComponent; + export const MedicineBoxOutlined: IconComponent; + export const MehOutlined: IconComponent; + export const MessageOutlined: IconComponent; + export const MobileOutlined: IconComponent; + export const MoneyCollectOutlined: IconComponent; + export const NotificationOutlined: IconComponent; + export const OrderedListOutlined: IconComponent; + export const PaperClipOutlined: IconComponent; + export const PartitionOutlined: IconComponent; + export const PauseOutlined: IconComponent; + export const PhoneOutlined: IconComponent; + export const PictureOutlined: IconComponent; + export const PlayCircleOutlined: IconComponent; + export const PlusOutlined: IconComponent; + export const PrinterOutlined: IconComponent; + export const ProfileOutlined: IconComponent; + export const ProjectOutlined: IconComponent; + export const PushpinOutlined: IconComponent; + export const QrcodeOutlined: IconComponent; + export const ReadOutlined: IconComponent; + export const ReconciliationOutlined: IconComponent; + export const RedEnvelopeOutlined: IconComponent; + export const ReloadOutlined: IconComponent; + export const RestOutlined: IconComponent; + export const RocketOutlined: IconComponent; + export const SafetyCertificateOutlined: IconComponent; + export const SaveOutlined: IconComponent; + export const ScheduleOutlined: IconComponent; + export const SecurityScanOutlined: IconComponent; + export const SettingOutlined: IconComponent; + export const ShopOutlined: IconComponent; + export const ShoppingCartOutlined: IconComponent; + export const SkinOutlined: IconComponent; + export const SmileOutlined: IconComponent; + export const SolutionOutlined: IconComponent; + export const StarOutlined: IconComponent; + export const StopOutlined: IconComponent; + export const SwitcherOutlined: IconComponent; + export const TabletOutlined: IconComponent; + export const TagOutlined: IconComponent; + export const TagsOutlined: IconComponent; + export const ToolOutlined: IconComponent; + export const UnlockOutlined: IconComponent; + export const UsergroupAddOutlined: IconComponent; + export const UsergroupDeleteOutlined: IconComponent; + export const UserOutlined: IconComponent; + export const VideoCameraOutlined: IconComponent; + export const WalletOutlined: IconComponent; + export const WarningOutlined: IconComponent; + export const WifiOutlined: IconComponent; + export const ZoomInOutlined: IconComponent; + export const ZoomOutOutlined: IconComponent; + } + \ No newline at end of file diff --git a/packages/fhir-import/package.json b/packages/fhir-import/package.json new file mode 100644 index 000000000..003709ead --- /dev/null +++ b/packages/fhir-import/package.json @@ -0,0 +1,47 @@ +{ + "name": "@opensrp/fhir-import", + "version": "0.0.6", + "description": "", + "main": "dist/index.js", + "types": "dist/types", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "repository": "https://github.com/opensrp/web", + "scripts": { + "test": "run -T test $INIT_CWD --verbose", + "tsc": "run -T tsc", + "lint": "run -T eslint ./**/*.{js,jsx,ts,tsx}", + "copy": "run -T copyfiles -u 1 \"./src/**/*.{css,html}\" \"./dist/\"", + "build": "run tsc && run transpile && run copy", + "transpile": "run -T babel src -d dist --root-mode upward --extensions .ts,.tsx --ignore '**/*.test.ts,**/*.test.tsx,**/tests,**/__tests__'" + }, + "jest": { + "automock": false, + "setupFiles": [ + "../../setupTests" + ] + }, + "bugs": { + "url": "https://github.com/opensrp/web/issues" + }, + "peerDependencies": { + "@opensrp/i18n": "^0.0.1", + "react": "^17.0.0", + "react-dom": "17.0.0", + "react-query": "^3.15.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0" + }, + "dependencies": { + "@2fd/ant-design-icons": "^2.6.0", + "@opensrp/notifications": "^0.0.5", + "@opensrp/rbac": "workspace:^", + "@opensrp/react-utils": "^0.0.12" + }, + "author": "OpenSRP Engineering", + "license": "Apache-2.0" +} diff --git a/packages/fhir-import/src/components/statusTag.tsx b/packages/fhir-import/src/components/statusTag.tsx new file mode 100644 index 000000000..b76375f0e --- /dev/null +++ b/packages/fhir-import/src/components/statusTag.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Tag } from 'antd'; + +export const ImportStatusTag = ({ statusString }: { statusString: JobStatus }) => { + const tagStatusColor = getStatusColor(statusString) + return ( + {statusString} + ) +} + +type JobStatus = + | 'completed' + | 'waiting' + | 'active' + | 'delayed' + | 'failed' + | 'paused'; + +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" + } +} \ No newline at end of file diff --git a/packages/fhir-import/src/constants.ts b/packages/fhir-import/src/constants.ts new file mode 100644 index 000000000..2e7ed6d69 --- /dev/null +++ b/packages/fhir-import/src/constants.ts @@ -0,0 +1,19 @@ +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 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/ImportDetailOverView/index.css b/packages/fhir-import/src/containers/ImportDetailOverView/index.css new file mode 100644 index 000000000..a316a09b2 --- /dev/null +++ b/packages/fhir-import/src/containers/ImportDetailOverView/index.css @@ -0,0 +1,23 @@ +.terminal-output { + color: #cccccc; + font-family: 'Courier New', Courier, monospace; + background-color: #1e1e1e; + border: 1px solid #555555; + border-radius: 5px; + padding: 20px; + margin: 20px auto; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + overflow: auto; + max-height: 600px; +} + +.terminal-output pre { + white-space: pre-wrap; + /* Makes sure that text wraps within the pre element */ + word-wrap: break-word; + word-break: break-all; + /* Ensures long words will wrap and not overflow the container */ + margin: 0; + color: #fff; + /* Color for the terminal text */ +} \ No newline at end of file diff --git a/packages/fhir-import/src/containers/ImportDetailOverView/index.tsx b/packages/fhir-import/src/containers/ImportDetailOverView/index.tsx new file mode 100644 index 000000000..8887c77c7 --- /dev/null +++ b/packages/fhir-import/src/containers/ImportDetailOverView/index.tsx @@ -0,0 +1,136 @@ +import React, { Fragment } from 'react'; +import { Col, Button, Alert, Spin, Tabs } from 'antd'; +import { CloseOutlined, SyncOutlined } from '@ant-design/icons'; +import { useQuery } from 'react-query'; +import { + BrokenPage, + FHIRServiceClass, + getObjLike, + IdentifierUseCodes, + getResourcesFromBundle, + parseFhirHumanName, + viewDetailsQuery, + useSearchParams, +} 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 { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; +import { formatTimestamp } from '../../helpers/utils' +import { getStatusColor } from '../../components/statusTag' +import "./index.css"; + +/** typings for the view details component */ +export interface RouteComponentProps { + workflowId: string; +} + +/** + * component that renders the details view to the right side + * of list view + * + * @param props - detail view component props + */ +export const ImportDetailViewDetails = (props: RouteComponentProps) => { + const params = useParams(); + const workflowId = params.workflowId; + const { t } = useTranslation(); + const { removeParam } = useSearchParams(); + console.log("We got here", { 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 ; + } + + if (error && !data) { + return ; + } + + if (!data) { + return ; + } + + const pageTitle = t(`View details | {{workflowId}}`, { workflowId: data.workflowId }); + const headerProps = { + pageHeaderProps: { + title: pageTitle, + onBack: undefined, + }, + }; + + + const dateCreatedKeyPairing = { + [t('Date Created')]: formatTimestamp(data.dateCreated), + }; + + const headerLeftData = { + ID: data.workflowId, + }; + + const otherDetailsMap = { + [t('Workflow type')]: data.workflowType, + [t('Date Started')]: formatTimestamp(data.dateStarted), + [t('Date Ended')]: formatTimestamp(data.dateEnded), + [t('Author')]: data.author + }; + + return ( + + + {pageTitle} + +
+ } + /> + +
+                {data.statusReason?.stdout}
+                {data.statusReason?.stderr}
+              
+
, + }]} + /> + +
+ ); +}; + diff --git a/packages/fhir-import/src/containers/ImportDetailOverView/tests/__snapshots__/index.test.tsx.snap b/packages/fhir-import/src/containers/ImportDetailOverView/tests/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..66bb3d084 --- /dev/null +++ b/packages/fhir-import/src/containers/ImportDetailOverView/tests/__snapshots__/index.test.tsx.snap @@ -0,0 +1,408 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`1157 - view details errors out for careTeam 3500 1`] = ` +
+
+ CareTeam ID +
+
+ 3500 +
+
+`; + +exports[`1157 - view details errors out for careTeam 3500 2`] = ` +
+
+ Name +
+
+ Peter James Charlmers Care team +
+
+`; + +exports[`1157 - view details errors out for careTeam 3500 3`] = ` +
+
+ status +
+
+ active +
+
+`; + +exports[`1157 - view details errors out for careTeam 3500 4`] = ` +
+
+ Participants +
+
+
    +
  • +
    +
    +
    +
    + Patient +
    +
    +
      +
    • + Peter James Chalmers +
    • +
    +
    +
    +
    +
    +
  • +
+
+
+`; + +exports[`1157 - view details errors out for careTeam 3500 5`] = ` +
+
+ Patient +
+
+
    +
  • + Peter James Chalmers +
  • +
+
+
+`; + +exports[`1157 - view details errors out for careTeam 3500 6`] = ` +
+
+ Managing organizations +
+
+ +
+
+`; + +exports[`Closes on clicking cancel (X) 1`] = ` +
+
+ CareTeam ID +
+
+ 142534 +
+
+`; + +exports[`Closes on clicking cancel (X) 2`] = ` +
+
+ Identifier +
+
+ 99c4dde5-3aca-4a4b-8b33-b50142e05da6 +
+
+`; + +exports[`Closes on clicking cancel (X) 3`] = ` +
+
+ Name +
+
+ Brown Bag +
+
+`; + +exports[`Closes on clicking cancel (X) 4`] = ` +
+
+ status +
+
+ active +
+
+`; + +exports[`Closes on clicking cancel (X) 5`] = ` +
+
+ Participants +
+
+
    +
  • +
    +
    +
    +
    + Practitioner +
    +
    +
      +
    • + AeHIN Demo +
    • +
    +
    +
    +
    +
    +
  • +
+
+
+`; + +exports[`Closes on clicking cancel (X) 6`] = ` +
+
+ Practitioner +
+
+
    +
  • + AeHIN Demo +
  • +
+
+
+`; + +exports[`Closes on clicking cancel (X) 7`] = ` +
+
+ Managing organizations +
+
+ +
+
+`; + +exports[`works correctly 1`] = ` +
+
+ CareTeam ID +
+
+ 131411 +
+
+`; + +exports[`works correctly 2`] = ` +
+
+ Identifier +
+
+ 93bc9c3d-6321-41b0-9b93-1275d7114e22 +
+
+`; + +exports[`works correctly 3`] = ` +
+
+ Name +
+
+ Care Team One +
+
+`; + +exports[`works correctly 4`] = ` +
+
+ status +
+
+ active +
+
+`; + +exports[`works correctly 5`] = ` +
+
+ Participants +
+
+
    +
  • +
    +
    +
    +
    + Practitioner +
    +
    +
      +
    • + Ward test hey N test Tester family +
    • +
    • + Ward test hey N test Tester family +
    • +
    +
    +
    +
    +
    +
  • +
+
+
+`; + +exports[`works correctly 6`] = ` +
+
+ Practitioner +
+
+
    +
  • + Ward test hey N test Tester family +
  • +
  • + Ward test hey N test Tester family +
  • +
+
+
+`; + +exports[`works correctly 7`] = ` +
+
+ Managing organizations +
+
+ +
+
+`; diff --git a/packages/fhir-import/src/containers/ImportDetailOverView/tests/fixtures.ts b/packages/fhir-import/src/containers/ImportDetailOverView/tests/fixtures.ts new file mode 100644 index 000000000..f0ac2811a --- /dev/null +++ b/packages/fhir-import/src/containers/ImportDetailOverView/tests/fixtures.ts @@ -0,0 +1,480 @@ +export const careTeamWithIncluded = { + resourceType: 'Bundle', + id: '2fb071f1-cafc-4a84-9d0d-e7bdb2e7875e', + meta: { + lastUpdated: '2022-09-29T20:32:11.641+00:00', + }, + type: 'searchset', + total: 1, + link: [ + { + relation: 'self', + url: 'https://fhir.labs.smartregister.org:443/fhir/CareTeam/_search?_id=131411&_include=CareTeam%3A*', + }, + ], + entry: [ + { + fullUrl: 'https://fhir.labs.smartregister.org:443/fhir/CareTeam/131411', + resource: { + resourceType: 'CareTeam', + id: '131411', + meta: { + versionId: '1', + lastUpdated: '2022-05-30T00:53:35.099+00:00', + source: '#8pT6h5Axyf9VsdQq', + }, + identifier: [ + { + use: 'official', + value: '93bc9c3d-6321-41b0-9b93-1275d7114e22', + }, + ], + status: 'active', + name: 'Care Team One', + subject: { + reference: 'Group/131410', + }, + participant: [ + { + member: { + reference: 'Practitioner/131406', + }, + }, + { + member: { + reference: 'Practitioner/131406', + }, + }, + ], + }, + search: { + mode: 'match', + }, + }, + { + fullUrl: 'https://fhir.labs.smartregister.org:443/fhir/Group/131410', + resource: { + resourceType: 'Group', + id: '131410', + meta: { + versionId: '2', + lastUpdated: '2022-06-27T03:22:27.188+00:00', + source: '#c6f633c24d9e6c4b', + }, + identifier: [ + { + use: 'official', + value: '93bc9c3d-6321-41b0-9b93-1275d7114e34', + }, + ], + active: true, + name: 'ANC patients', + quantity: 1, + member: [ + { + entity: { + reference: 'Patient/131408', + }, + }, + ], + }, + search: { + mode: 'include', + }, + }, + { + fullUrl: 'https://fhir.labs.smartregister.org:443/fhir/Practitioner/131406', + resource: { + resourceType: 'Practitioner', + id: '131406', + meta: { + versionId: '1', + lastUpdated: '2022-05-30T00:38:44.891+00:00', + source: '#R1qpXIa2QDrDkBrn', + }, + identifier: [ + { + use: 'official', + value: 'aace2e430b-64be-477e-9d86-b36c666c0211', + }, + { + use: 'secondary', + value: '40353ad0-6fa0-4da3-9dd6-b2d9d5a09b6a', + }, + ], + active: true, + name: [ + { + use: 'official', + family: 'Tester family', + given: ['Ward test hey', 'N test'], + }, + ], + telecom: [ + { + system: 'email', + value: 'reham.muzzamil@venturedive.com', + }, + ], + }, + search: { + mode: 'include', + }, + }, + ], +}; + +export const careTeam2 = { + resourceType: 'Bundle', + id: '11112110-5942-40cb-b8ca-80650821dba4', + meta: { + lastUpdated: '2022-09-30T06:13:08.311+00:00', + }, + type: 'searchset', + total: 1, + link: [ + { + relation: 'self', + url: 'https://fhir.labs.smartregister.org:443/fhir/CareTeam?_format=json&_id=142534&_include=CareTeam%3A*', + }, + ], + entry: [ + { + fullUrl: 'https://fhir.labs.smartregister.org:443/fhir/CareTeam/142534', + resource: { + resourceType: 'CareTeam', + id: '142534', + meta: { + versionId: '1', + lastUpdated: '2022-09-01T12:44:38.522+00:00', + source: '#532e59e3409867b3', + }, + identifier: [ + { + use: 'official', + value: '99c4dde5-3aca-4a4b-8b33-b50142e05da6', + }, + ], + status: 'active', + name: 'Brown Bag', + participant: [ + { + member: { + reference: 'Practitioner/137469', + }, + }, + ], + }, + search: { + mode: 'match', + }, + }, + { + fullUrl: 'https://fhir.labs.smartregister.org:443/fhir/Practitioner/137469', + resource: { + resourceType: 'Practitioner', + id: '137469', + meta: { + versionId: '1', + lastUpdated: '2022-08-10T13:16:53.838+00:00', + source: '#2c648422b7a6e78e', + }, + identifier: [ + { + use: 'official', + value: '175ceaa4-0f75-4ab3-a8a7-413cc225f761', + }, + { + use: 'secondary', + value: 'b27939dd-4c8f-44c2-83dd-dc40e494f17d', + }, + ], + active: true, + name: [ + { + use: 'official', + family: 'Demo', + given: ['AeHIN'], + }, + ], + telecom: [ + { + system: 'email', + }, + ], + }, + search: { + mode: 'include', + }, + }, + ], +}; + +export const careTeam3500 = { + resourceType: 'Bundle', + id: '964d0cdc-3297-41fa-991f-47d639ff7635', + meta: { + lastUpdated: '2023-02-10T08:22:04.594+00:00', + }, + type: 'searchset', + total: 1, + link: [ + { + relation: 'self', + url: 'https://fhir.labs.smartregister.org/fhir/CareTeam/_search?_id=3500&_include=CareTeam%3A*', + }, + ], + entry: [ + { + fullUrl: 'https://fhir.labs.smartregister.org/fhir/CareTeam/3500', + resource: { + resourceType: 'CareTeam', + id: '3500', + meta: { + versionId: '1', + lastUpdated: '2021-10-12T07:29:44.733+00:00', + source: '#9837ac48046ef77c', + }, + text: { + status: 'generated', + div: '
Care Team
', + }, + contained: [ + { + resourceType: 'Practitioner', + id: '3457', + name: [ + { + family: 'Careful', + given: ['Adam'], + prefix: ['Dr'], + }, + ], + }, + ], + status: 'active', + category: [ + { + coding: [ + { + system: 'http://loinc.org', + code: 'LA27976-2', + display: 'Encounter-focused care team', + }, + ], + }, + ], + name: 'Peter James Charlmers Care team', + subject: { + reference: 'Patient/3455', + display: 'Peter James Chalmers', + }, + encounter: { + reference: 'Encounter/3458', + }, + period: { + end: '2013-01-01', + }, + participant: [ + { + role: [ + { + text: 'responsiblePerson', + }, + ], + member: { + reference: 'Patient/3455', + display: 'Peter James Chalmers', + }, + }, + { + role: [ + { + text: 'adviser', + }, + ], + member: { + reference: '#pr1', + display: 'Dorothy Dietition', + }, + onBehalfOf: { + reference: 'Organization/0000', + }, + period: { + end: '2013-01-01', + }, + }, + ], + managingOrganization: [ + { + reference: 'Organization/3461', + }, + ], + }, + search: { + mode: 'match', + }, + }, + { + fullUrl: 'https://fhir.labs.smartregister.org/fhir/Encounter/3458', + resource: { + resourceType: 'Encounter', + id: '3458', + meta: { + versionId: '2', + lastUpdated: '2021-10-25T06:53:54.230+00:00', + source: '#1350520584a57b46', + }, + text: { + status: 'generated', + div: '
Encounter with patient @example
', + }, + status: 'finished', + class: { + system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', + code: 'IMP', + display: 'inpatient encounter to check on Obsesity', + }, + subject: { + reference: 'Patient/3455', + }, + }, + search: { + mode: 'include', + }, + }, + { + fullUrl: 'https://fhir.labs.smartregister.org/fhir/Patient/3455', + resource: { + resourceType: 'Patient', + id: '3455', + meta: { + versionId: '3', + lastUpdated: '2021-10-22T13:49:19.121+00:00', + source: '#70532eaf6e0ba7df', + }, + text: { + status: 'generated', + div: '
Peter James CHALMERS
Address534 Erewhon St
PleasantVille Vic
Date of birth25 December 1974
', + }, + active: true, + name: [ + { + use: 'official', + family: 'Chalmers', + given: ['Peter', 'James'], + }, + { + use: 'usual', + given: ['Jim'], + }, + { + use: 'maiden', + family: 'Windsor', + given: ['Peter', 'James'], + period: { + end: '2002', + }, + }, + ], + telecom: [ + { + use: 'home', + }, + { + system: 'phone', + value: '(03) 5555 6473', + use: 'work', + rank: 1, + }, + { + system: 'phone', + value: '(03) 3410 5613', + use: 'mobile', + rank: 2, + }, + { + system: 'phone', + value: '(03) 5555 8834', + use: 'old', + period: { + end: '2014', + }, + }, + ], + gender: 'male', + birthDate: '1974-12-25', + deceasedBoolean: false, + address: [ + { + use: 'home', + type: 'both', + text: '534 Erewhon St PeasantVille, Rainbow, Vic 3999', + line: ['534 Erewhon St'], + city: 'PleasantVille', + district: 'Rainbow', + state: 'Vic', + postalCode: '3999', + period: { + start: '1974-12-25', + }, + }, + ], + contact: [ + { + relationship: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0131', + code: 'N', + }, + ], + }, + ], + name: { + family: 'du Marché', + _family: { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/humanname-own-prefix', + valueString: 'VV', + }, + ], + }, + given: ['Bénédicte'], + }, + telecom: [ + { + system: 'phone', + value: '+33 (237) 998327', + }, + ], + address: { + use: 'home', + type: 'both', + line: ['534 Erewhon St'], + city: 'PleasantVille', + district: 'Rainbow', + state: 'Vic', + postalCode: '3999', + period: { + start: '1974-12-25', + }, + }, + gender: 'female', + period: { + start: '2012', + }, + }, + ], + managingOrganization: { + reference: 'Organization/3454', + }, + }, + search: { + mode: 'include', + }, + }, + ], +}; diff --git a/packages/fhir-import/src/containers/ImportDetailOverView/tests/index.test.tsx b/packages/fhir-import/src/containers/ImportDetailOverView/tests/index.test.tsx new file mode 100644 index 000000000..0693dc5a3 --- /dev/null +++ b/packages/fhir-import/src/containers/ImportDetailOverView/tests/index.test.tsx @@ -0,0 +1,155 @@ +import { store } from '@opensrp/store'; +import { authenticateUser } from '@onaio/session-reducer'; +import React from 'react'; +import { Router } from 'react-router'; +import { QueryClientProvider } from 'react-query'; +import { ViewDetails } from '..'; +import { careTeam2, careTeam3500, careTeamWithIncluded } from './fixtures'; +import { createBrowserHistory } from 'history'; +import { createTestQueryClient } from '../../ListView/tests/utils'; +import nock from 'nock'; +import { cleanup, fireEvent, render, waitForElementToBeRemoved } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import { URL_CARE_TEAM, careTeamResourceType } from '../../../constants'; + +const history = createBrowserHistory(); + +const testQueryClient = createTestQueryClient(); + +jest.mock('fhirclient', () => { + return jest.requireActual('fhirclient/lib/entry/browser'); +}); + +jest.mock('@opensrp/notifications', () => ({ + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/notifications')), +})); + +const props = { + fhirBaseURL: 'http://test.server.org', + careTeamId: '131411', +}; + +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' } } + ) + ); +}); + +afterEach(() => { + nock.cleanAll(); + cleanup(); +}); + +afterAll(() => { + nock.enableNetConnect(); +}); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const AppWrapper = (props: any) => { + return ( + + + + + + ); +}; + +test('works correctly', async () => { + nock(props.fhirBaseURL) + .get(`/${careTeamResourceType}/_search`) + .query({ _id: '131411', _include: 'CareTeam:*' }) + .reply(200, careTeamWithIncluded); + + const { queryByText } = render(); + await waitForElementToBeRemoved(queryByText(/Fetching Care team/i)); + + // see view details contents + const keyValuePairs = document.querySelectorAll( + 'div[data-testid="key-value"] .singleKeyValue-pair__default' + ); + keyValuePairs.forEach((pair) => { + expect(pair).toMatchSnapshot(); + }); + + expect(nock.pendingMocks()).toEqual([]); +}); + +test('Closes on clicking cancel (X) ', async () => { + const history = createMemoryHistory(); + history.push(URL_CARE_TEAM); + + const localProps = { + ...props, + careTeamId: '142534', + }; + + nock(props.fhirBaseURL) + .get(`/${careTeamResourceType}/_search`) + .query({ _id: localProps.careTeamId, _include: 'CareTeam:*' }) + .reply(200, careTeam2); + + const { queryByText } = render(); + await waitForElementToBeRemoved(queryByText(/Fetching Care team/i)); + + // see view details contents + const keyValuePairs = document.querySelectorAll( + 'div[data-testid="key-value"] .singleKeyValue-pair__default' + ); + keyValuePairs.forEach((pair) => { + expect(pair).toMatchSnapshot(); + }); + + // simulate clicking on close button + const button = document.querySelector('.flex-right button'); + fireEvent.click(button); + + expect(history.location.pathname).toEqual('/admin/CareTeams'); +}); + +test('shows broken page if fhir api is down', async () => { + nock(props.fhirBaseURL) + .get(`/${careTeamResourceType}/_search`) + .query({ _id: props.careTeamId, _include: 'CareTeam:*' }) + .replyWithError('coughid'); + + const { getByText, queryByText } = render(); + await waitForElementToBeRemoved(queryByText(/Fetching Care team/i)); + + expect(getByText(/coughid/)).toBeInTheDocument(); +}); + +test('1157 - view details errors out for careTeam 3500', async () => { + const thisProps = { + ...props, + careTeamId: '3500', + }; + nock(props.fhirBaseURL) + .get(`/${careTeamResourceType}/_search`) + .query({ _id: '3500', _include: 'CareTeam:*' }) + .reply(200, careTeam3500); + + const { queryByText } = render(); + await waitForElementToBeRemoved(queryByText(/Fetching Care team/i)); + + // see view details contents + const keyValuePairs = document.querySelectorAll( + 'div[data-testid="key-value"] .singleKeyValue-pair__default' + ); + keyValuePairs.forEach((pair) => { + expect(pair).toMatchSnapshot(); + }); + + expect(nock.pendingMocks()).toEqual([]); +}); diff --git a/packages/fhir-import/src/containers/ImportListView/index.tsx b/packages/fhir-import/src/containers/ImportListView/index.tsx new file mode 100644 index 000000000..7fc22c0ae --- /dev/null +++ b/packages/fhir-import/src/containers/ImportListView/index.tsx @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { Row, Col, Button, Divider, Dropdown, Popconfirm, Tag, Space } from 'antd'; +import type { MenuProps } from 'antd'; +import { BodyLayout, OpenSRPService } from '@opensrp/react-utils'; +import { MoreOutlined, CloudUploadOutlined } from '@ant-design/icons'; +import { RouteComponentProps } from 'react-router'; +import { useHistory, Link } from 'react-router-dom'; +import { + FHIRServiceClass, + useSimpleTabularView, + BrokenPage, + SearchForm, + TableLayout, + useSearchParams, + viewDetailsQuery, +} from '@opensrp/react-utils'; +import { + DATA_IMPORT_LIST_URL, + DATA_IMPORT_DETAIL_URL, + DATA_IMPORT_CREATE_URL, + dataImportRQueryKey, + IMPORT_DOMAIN_URI +} from '../../constants'; +import { ImportDetailViewDetails } from '../ImportDetailOverView'; +import { Dictionary } from '@onaio/utils'; +import { sendErrorNotification, sendSuccessNotification } from '@opensrp/notifications'; +import { useTranslation } from '../../mls'; +import type { TFunction } from '@opensrp/i18n'; +import { ICareTeam } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/ICareTeam'; +import { RbacCheck, useUserRole } from '@opensrp/rbac'; +import { useQuery } from 'react-query'; +import { customFetch } from 'opensrp-server-service/dist/types'; +import { WorkflowDescription, formatTimestamp } from '../../helpers/utils'; +import { SortOrder } from 'antd/es/table/interface'; +import { ImportStatusTag } from '../../components/statusTag' + + +// route params for care team pages +interface RouteParams { + workflowId: string | undefined; +} + +interface Props { } + +export type ImportListPropTypes = Props & RouteComponentProps; + + + + +/** + * Function which shows the list of all roles and their details + * + * @param {Object} props - UserRolesList component props + * @returns {Function} returns User Roles list display + */ +export const DataImportList: React.FC = (props: ImportListPropTypes) => { + const { t } = useTranslation(); + const history = useHistory(); + + const { sParams } = useSearchParams() + const resourceId = sParams.get(viewDetailsQuery) ?? undefined; + + const { data, isFetching, isLoading, error } = useQuery(dataImportRQueryKey, () => { + const service = new OpenSRPService("/$import", IMPORT_DOMAIN_URI) + return service.list().then(res => { + return res + }) + }) + + if (error && !data) { + return ; + } + + + // TODO - add sort + const columns = [ + { + title: t('Workflow Id'), + dataIndex: 'workflowId' as const, + }, + { + title: t('Resource upload'), + dataIndex: 'workflowType' as const, + }, + { + title: t('File name'), + dataIndex: 'filename' as const, + }, + { + title: t('status'), + dataIndex: 'status' as const, + render: (_: any) => { + 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 + }, + render: (_: any) => formatTimestamp(_), + }, + { + title: t('Actions'), + width: '10%', + + // eslint-disable-next-line react/display-name + render: (_: unknown, record: WorkflowDescription) => ( + + + <> + + {t('View')} + + + + + ), + }, + ]; + + const tableProps = { + datasource: (data ?? []), + columns, + loading: isFetching || isLoading, // TODO - add pagination + }; + const pageTitle = t('Data imports'); + const headerProps = { + pageHeaderProps: { + title: pageTitle, + onBack: undefined, + }, + }; + + return ( + + + {pageTitle} + + + +
+
+ + + + + +
+ + + {resourceId && } + + + ); +}; diff --git a/packages/fhir-import/src/containers/ImportListView/tests/__snapshots__/index-rtl.test.tsx.snap b/packages/fhir-import/src/containers/ImportListView/tests/__snapshots__/index-rtl.test.tsx.snap new file mode 100644 index 000000000..b10ae19dc --- /dev/null +++ b/packages/fhir-import/src/containers/ImportListView/tests/__snapshots__/index-rtl.test.tsx.snap @@ -0,0 +1,301 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Care Teams list view renders correctly: table row 1 page 1 1`] = ` + + Care Team One + +`; + +exports[`Care Teams list view renders correctly: table row 1 page 1 2`] = ` + + + + Edit + +