@openmrs/esm-openconceptlab-app 3.0.1-pre.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +36 -0
- package/LICENSE +401 -0
- package/README.md +8 -0
- package/dist/316.js +2 -0
- package/dist/316.js.LICENSE.txt +19 -0
- package/dist/452.js +1 -0
- package/dist/574.js +1 -0
- package/dist/645.js +2 -0
- package/dist/645.js.LICENSE.txt +20 -0
- package/dist/709.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/858.js +2 -0
- package/dist/858.js.LICENSE.txt +1 -0
- package/dist/862.js +1 -0
- package/dist/main.js +1 -0
- package/dist/openmrs-esm-openconceptlab-app.js +1 -0
- package/dist/openmrs-esm-openconceptlab-app.js.buildmanifest.json +265 -0
- package/dist/openmrs-esm-openconceptlab-app.old +1 -0
- package/package.json +53 -0
- package/src/config-schema.ts +5 -0
- package/src/declarations.d.tsx +2 -0
- package/src/import/import.component.scss +27 -0
- package/src/import/import.component.tsx +190 -0
- package/src/import/import.resource.ts +40 -0
- package/src/import/import.test.tsx +103 -0
- package/src/index.ts +61 -0
- package/src/previous-imports/import-overview/import-items.component.scss +24 -0
- package/src/previous-imports/import-overview/import-items.component.tsx +124 -0
- package/src/previous-imports/import-overview/import-items.resource.ts +10 -0
- package/src/previous-imports/import-overview/import-items.test.tsx +93 -0
- package/src/previous-imports/import-overview/import-overview.component.scss +11 -0
- package/src/previous-imports/import-overview/import-overview.component.tsx +65 -0
- package/src/previous-imports/import-overview/import-overview.test.tsx +83 -0
- package/src/previous-imports/previous-imports.component.scss +39 -0
- package/src/previous-imports/previous-imports.component.tsx +136 -0
- package/src/previous-imports/previous-imports.resource.ts +17 -0
- package/src/previous-imports/previous-imports.test.tsx +77 -0
- package/src/root.component.scss +23 -0
- package/src/root.component.tsx +39 -0
- package/src/root.test.tsx +22 -0
- package/src/setup-tests.ts +1 -0
- package/src/subscription/subscription.component.scss +27 -0
- package/src/subscription/subscription.component.tsx +244 -0
- package/src/subscription/subscription.resource.ts +40 -0
- package/src/subscription/subscription.test.tsx +181 -0
- package/src/types.ts +34 -0
- package/translations/en.json +51 -0
- package/tsconfig.json +23 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import Import from './import.component';
|
|
5
|
+
import { openmrsFetch, showNotification } from '@openmrs/esm-framework';
|
|
6
|
+
import { renderWithSwr } from '../../../../tools/test-helpers';
|
|
7
|
+
import { startImportWithSubscription } from './import.resource';
|
|
8
|
+
import { mockSubscription } from '../../../../__mocks__/openconceptlab.mock';
|
|
9
|
+
|
|
10
|
+
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
|
|
11
|
+
const mockStartImportWithSubscription = startImportWithSubscription as jest.Mock;
|
|
12
|
+
const mockShowNotification = showNotification as jest.Mock;
|
|
13
|
+
|
|
14
|
+
jest.mock('./import.resource', () => {
|
|
15
|
+
const originalModule = jest.requireActual('./import.resource');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
...originalModule,
|
|
19
|
+
startImportWithSubscription: jest.fn(),
|
|
20
|
+
startImportWithFile: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
25
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...originalModule,
|
|
29
|
+
showNotification: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe(`Import component`, () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
mockShowNotification.mockReset();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it(`renders without dying`, () => {
|
|
39
|
+
renderImportComponent();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it(`renders the form elements`, async () => {
|
|
43
|
+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
|
|
44
|
+
renderImportComponent();
|
|
45
|
+
await waitForLoadingToFinish();
|
|
46
|
+
|
|
47
|
+
expect(screen.getByText('Import Concepts')).toBeVisible();
|
|
48
|
+
expect(screen.getByText('Import from Subscription')).toBeVisible();
|
|
49
|
+
|
|
50
|
+
expect(screen.getByText('Import from file (Offline)')).toBeVisible();
|
|
51
|
+
expect(screen.getByText('Import from file')).toBeEnabled();
|
|
52
|
+
expect(screen.queryByText('File Added')).not.toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it(`renders correctly when there is no subscription`, async () => {
|
|
56
|
+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
|
|
57
|
+
renderImportComponent();
|
|
58
|
+
await waitForLoadingToFinish();
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText('Import from Subscription')).toBeDisabled();
|
|
61
|
+
expect(screen.getByText('Import from file')).toBeEnabled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it(`renders correctly when when a subscription exists`, async () => {
|
|
65
|
+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [mockSubscription] } });
|
|
66
|
+
renderImportComponent();
|
|
67
|
+
await waitForLoadingToFinish();
|
|
68
|
+
|
|
69
|
+
await waitFor(() => expect(screen.getByText('Import from Subscription')).toBeEnabled(), { timeout: 2000 });
|
|
70
|
+
expect(screen.getByText('Import from file')).toBeEnabled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it(`allows starting an import using the subscription`, async () => {
|
|
74
|
+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [mockSubscription] } });
|
|
75
|
+
renderImportComponent();
|
|
76
|
+
await waitForLoadingToFinish();
|
|
77
|
+
|
|
78
|
+
mockStartImportWithSubscription.mockReturnValueOnce({ status: 201 });
|
|
79
|
+
|
|
80
|
+
await waitFor(() => userEvent.click(screen.getByText('Import from Subscription')));
|
|
81
|
+
|
|
82
|
+
expect(mockStartImportWithSubscription).toHaveBeenCalledWith(new AbortController());
|
|
83
|
+
expect(mockStartImportWithSubscription).toHaveBeenCalledTimes(1);
|
|
84
|
+
|
|
85
|
+
expect(mockShowNotification).toHaveBeenCalledWith(
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
description: 'Import started successfully',
|
|
88
|
+
kind: 'success',
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
expect(mockShowNotification).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function renderImportComponent() {
|
|
96
|
+
renderWithSwr(<Import />);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function waitForLoadingToFinish() {
|
|
100
|
+
return waitFor(() => {
|
|
101
|
+
expect(screen.getByText('Import Concepts')).toBeVisible, { timeout: 2000 };
|
|
102
|
+
});
|
|
103
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the entrypoint file of the application. It communicates the
|
|
3
|
+
* important features of this microfrontend to the app shell. It
|
|
4
|
+
* connects the app shell to the React application(s) that make up this
|
|
5
|
+
* microfrontend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
|
|
9
|
+
import { configSchema } from './config-schema';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This tells the app shell how to obtain translation files: that they
|
|
13
|
+
* are JSON files in the directory `../translations` (which you should
|
|
14
|
+
* see in the directory structure).
|
|
15
|
+
*/
|
|
16
|
+
const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This tells the app shell what versions of what OpenMRS backend modules
|
|
20
|
+
* are expected. Warnings will appear if suitable modules are not
|
|
21
|
+
* installed. The keys are the part of the module name after
|
|
22
|
+
* `openmrs-module-`; e.g., `openmrs-module-fhir2` becomes `fhir2`.
|
|
23
|
+
*/
|
|
24
|
+
const backendDependencies = {
|
|
25
|
+
openconceptlab: '^1.2.0',
|
|
26
|
+
'webservices.rest': '^2.2.0',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This function performs any setup that should happen at microfrontend
|
|
31
|
+
* load-time (such as defining the config schema) and then returns an
|
|
32
|
+
* object which describes how the React application(s) should be
|
|
33
|
+
* rendered.
|
|
34
|
+
*
|
|
35
|
+
* In this example, our return object contains a single page definition.
|
|
36
|
+
* It tells the app shell that the default export of `greeter.tsx`
|
|
37
|
+
* should be rendered when the route matches `hello`. The full route
|
|
38
|
+
* will be `openmrsSpaBase() + 'hello'`, which is usually
|
|
39
|
+
* `/openmrs/spa/hello`.
|
|
40
|
+
*/
|
|
41
|
+
function setupOpenMRS() {
|
|
42
|
+
const moduleName = '@openmrs/esm-openconceptlab-app';
|
|
43
|
+
|
|
44
|
+
const options = {
|
|
45
|
+
featureName: 'openconceptlab',
|
|
46
|
+
moduleName,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
defineConfigSchema(moduleName, configSchema);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
pages: [
|
|
53
|
+
{
|
|
54
|
+
load: getAsyncLifecycle(() => import('./root.component'), options),
|
|
55
|
+
route: 'ocl',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { backendDependencies, importTranslation, setupOpenMRS };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@use "@carbon/styles/scss/spacing";
|
|
2
|
+
@use "@carbon/styles/scss/type";
|
|
3
|
+
@import "~@openmrs/esm-styleguide/src/vars";
|
|
4
|
+
|
|
5
|
+
.pagination {
|
|
6
|
+
@include type.type-style('body-short-01');
|
|
7
|
+
background-color: $ui-02;
|
|
8
|
+
color: $text-02;
|
|
9
|
+
border: 1px solid $ui-03;
|
|
10
|
+
display: flex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.tableBordered {
|
|
14
|
+
border: 1px solid $ui-03;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.tableDataRow > td {
|
|
18
|
+
padding-left: 1rem !important;
|
|
19
|
+
background-color: $ui-02 !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tableHeaderRow > th {
|
|
23
|
+
background-color: $ui-03 !important;
|
|
24
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { showNotification, usePagination } from '@openmrs/esm-framework';
|
|
2
|
+
import {
|
|
3
|
+
DataTableSkeleton,
|
|
4
|
+
Link,
|
|
5
|
+
Pagination,
|
|
6
|
+
PaginationSkeleton,
|
|
7
|
+
Table,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
|
+
} from '@carbon/react';
|
|
14
|
+
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import { getImportDetails } from './import-items.resource';
|
|
17
|
+
import styles from './import-items.component.scss';
|
|
18
|
+
import { ImportItem } from '../../types';
|
|
19
|
+
|
|
20
|
+
interface ImportItemsProps {
|
|
21
|
+
importUuid: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ImportItems: React.FC<ImportItemsProps> = ({ importUuid }) => {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const [isLoading, setIsLoading] = useState<Boolean>();
|
|
27
|
+
const [pageSize, setPageSize] = useState(5);
|
|
28
|
+
|
|
29
|
+
const [selectedImportItems, setSelectedImportItems] = useState<ImportItem[]>([]);
|
|
30
|
+
const { results, currentPage, goTo } = usePagination(selectedImportItems, pageSize);
|
|
31
|
+
|
|
32
|
+
const handleImportDetails = useCallback(
|
|
33
|
+
async (uuid: string) => {
|
|
34
|
+
setIsLoading(true);
|
|
35
|
+
const abortController = new AbortController();
|
|
36
|
+
|
|
37
|
+
const response = await getImportDetails(uuid, abortController);
|
|
38
|
+
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
setSelectedImportItems(response.data.results);
|
|
41
|
+
} else {
|
|
42
|
+
showNotification({
|
|
43
|
+
title: t('importItemsFetchError', 'Error occured while fetching the import items'),
|
|
44
|
+
kind: 'error',
|
|
45
|
+
critical: true,
|
|
46
|
+
description: JSON.stringify(response.data),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
setIsLoading(false);
|
|
50
|
+
return () => abortController.abort();
|
|
51
|
+
},
|
|
52
|
+
[t],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
handleImportDetails(importUuid);
|
|
57
|
+
}, [handleImportDetails, importUuid]);
|
|
58
|
+
|
|
59
|
+
if (isLoading) {
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<DataTableSkeleton showHeader={false} showToolbar={false} columnCount={2} />
|
|
63
|
+
<PaginationSkeleton />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headerData = [
|
|
69
|
+
{
|
|
70
|
+
header: t('conceptOrMapping', 'Concept/Mapping'),
|
|
71
|
+
key: 'uuid',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
header: t('message', 'Message'),
|
|
75
|
+
key: 'errorMessage',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
!isLoading && (
|
|
81
|
+
<div>
|
|
82
|
+
<Table size="sm" className={styles.tableBordered}>
|
|
83
|
+
<TableHead>
|
|
84
|
+
<TableRow className={styles.tableHeaderRow}>
|
|
85
|
+
{headerData.map((header, i) => (
|
|
86
|
+
<TableHeader key={i} isSortable={true}>
|
|
87
|
+
{header.header}
|
|
88
|
+
</TableHeader>
|
|
89
|
+
))}
|
|
90
|
+
</TableRow>
|
|
91
|
+
</TableHead>
|
|
92
|
+
<TableBody>
|
|
93
|
+
{results?.map((row) => (
|
|
94
|
+
<Fragment key={row.uuid}>
|
|
95
|
+
<TableRow className={styles.tableDataRow}>
|
|
96
|
+
<TableCell>
|
|
97
|
+
<Link href={row.versionUrl}>
|
|
98
|
+
{row.type} {row.uuid}
|
|
99
|
+
</Link>
|
|
100
|
+
</TableCell>
|
|
101
|
+
<TableCell>{row.errorMessage}</TableCell>
|
|
102
|
+
</TableRow>
|
|
103
|
+
</Fragment>
|
|
104
|
+
))}
|
|
105
|
+
</TableBody>
|
|
106
|
+
</Table>
|
|
107
|
+
<Pagination
|
|
108
|
+
className={styles.pagination}
|
|
109
|
+
size="sm"
|
|
110
|
+
page={currentPage}
|
|
111
|
+
pageSize={pageSize}
|
|
112
|
+
pageSizes={[5, 10, 20, 50, 100]}
|
|
113
|
+
totalItems={selectedImportItems?.length}
|
|
114
|
+
onChange={({ page, pageSize }) => {
|
|
115
|
+
goTo(page);
|
|
116
|
+
setPageSize(pageSize);
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default ImportItems;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
2
|
+
import { ImportItem } from '../../types';
|
|
3
|
+
|
|
4
|
+
export async function getImportDetails(importUuid: string, abortController?: AbortController) {
|
|
5
|
+
const url = `/ws/rest/v1/openconceptlab/import/${importUuid}/item?state=ERROR&v=full`;
|
|
6
|
+
return openmrsFetch<{ results: ImportItem[] }>(url, {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
signal: abortController?.signal,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { usePagination } from '@openmrs/esm-framework';
|
|
4
|
+
import { renderWithSwr } from '../../../../../tools/test-helpers';
|
|
5
|
+
import { mockImportItems, mockPreviousImports } from '../../../../../__mocks__/openconceptlab.mock';
|
|
6
|
+
import { ImportItem } from '../../types';
|
|
7
|
+
import ImportItems from './import-items.component';
|
|
8
|
+
import { getImportDetails } from './import-items.resource';
|
|
9
|
+
|
|
10
|
+
const testProps = {
|
|
11
|
+
importUuid: mockPreviousImports[1].uuid,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockGetImportDetails = getImportDetails as jest.Mock;
|
|
15
|
+
const mockUsePagination = usePagination as jest.Mock;
|
|
16
|
+
|
|
17
|
+
jest.mock('./import-items.resource', () => {
|
|
18
|
+
const originalModule = jest.requireActual('./import-items.resource');
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
...originalModule,
|
|
22
|
+
getImportDetails: jest.fn(),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
27
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
...originalModule,
|
|
31
|
+
usePagination: jest.fn(),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe(`Import Items component`, () => {
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
mockGetImportDetails.mockReset();
|
|
38
|
+
mockUsePagination.mockReset();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it(`renders without dying`, () => {
|
|
42
|
+
mockGetImportDetails.mockReturnValue({ status: 200, ok: true, data: [] });
|
|
43
|
+
mockUsePagination.mockReturnValue({
|
|
44
|
+
currentPage: 1,
|
|
45
|
+
goTo: () => {},
|
|
46
|
+
results: [],
|
|
47
|
+
});
|
|
48
|
+
renderImportItemsComponent();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it(`renders the table`, async () => {
|
|
52
|
+
mockGetImportDetails.mockReturnValue({ status: 200, ok: true, data: mockImportItems });
|
|
53
|
+
mockUsePagination.mockReturnValue({
|
|
54
|
+
currentPage: 1,
|
|
55
|
+
goTo: () => {},
|
|
56
|
+
results: mockImportItems,
|
|
57
|
+
});
|
|
58
|
+
renderImportItemsComponent();
|
|
59
|
+
await waitForLoadingToFinish();
|
|
60
|
+
|
|
61
|
+
expect(screen.getByText('Concept/Mapping')).toBeVisible();
|
|
62
|
+
expect(screen.getByText('Message')).toBeVisible();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it(`renders the import items correctly`, async () => {
|
|
66
|
+
mockGetImportDetails.mockReturnValue({ status: 200, ok: true, data: mockImportItems });
|
|
67
|
+
mockUsePagination.mockReturnValue({
|
|
68
|
+
currentPage: 1,
|
|
69
|
+
goTo: () => {},
|
|
70
|
+
results: mockImportItems,
|
|
71
|
+
});
|
|
72
|
+
renderImportItemsComponent();
|
|
73
|
+
await waitForLoadingToFinish();
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText('Concept/Mapping')).toBeVisible();
|
|
76
|
+
expect(screen.getByText('Message')).toBeVisible();
|
|
77
|
+
|
|
78
|
+
mockImportItems.slice(5).forEach((importItem: ImportItem) => {
|
|
79
|
+
expect(screen.getByText(importItem.type + ' ' + importItem.uuid)).toBeVisible();
|
|
80
|
+
expect(screen.getByText(importItem.errorMessage)).toBeVisible();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function renderImportItemsComponent() {
|
|
86
|
+
renderWithSwr(<ImportItems {...testProps} />);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function waitForLoadingToFinish() {
|
|
90
|
+
return waitFor(() => {
|
|
91
|
+
expect(screen.getByText('Concept/Mapping')).toBeVisible(), { timeout: 2000 };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { formatDatetime } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import styles from './import-overview.component.scss';
|
|
5
|
+
import { Import } from '../../types';
|
|
6
|
+
import ImportItems from './import-items.component';
|
|
7
|
+
|
|
8
|
+
interface ImportOverviewProps {
|
|
9
|
+
selectedImportObject: Import;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ImportOverview: React.FC<ImportOverviewProps> = ({ selectedImportObject }) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<span className={styles.heading01}>{t('startedOn', 'Started on')} </span>
|
|
18
|
+
<span className="body">{formatDatetime(new Date(selectedImportObject.localDateStarted))}</span>
|
|
19
|
+
<br />
|
|
20
|
+
|
|
21
|
+
<span className={styles.heading01}>{t('completedOn', 'Completed on')} </span>
|
|
22
|
+
<span className="body">{formatDatetime(new Date(selectedImportObject.localDateStopped))}</span>
|
|
23
|
+
<br />
|
|
24
|
+
|
|
25
|
+
<span className={styles.heading01}>{t('duration', 'Duration')} </span>
|
|
26
|
+
<span className="body">{selectedImportObject.importTime}</span>
|
|
27
|
+
<br />
|
|
28
|
+
<br />
|
|
29
|
+
|
|
30
|
+
<span className={styles.heading01}>{t('result', 'Result:')} </span>
|
|
31
|
+
<br />
|
|
32
|
+
|
|
33
|
+
<div className={`body ${styles.indentedContent}`}>
|
|
34
|
+
<span>
|
|
35
|
+
{selectedImportObject.upToDateItemsCount} {t('conceptsUpToDate', 'concepts up to date')}
|
|
36
|
+
</span>
|
|
37
|
+
<br />
|
|
38
|
+
<span>
|
|
39
|
+
{selectedImportObject.addedItemsCount} {t('conceptsAdded', 'concepts added')}
|
|
40
|
+
</span>
|
|
41
|
+
<br />
|
|
42
|
+
<span>
|
|
43
|
+
{selectedImportObject.updatedItemsCount} {t('conceptsUpdated', 'concepts updated')}
|
|
44
|
+
</span>
|
|
45
|
+
<br />
|
|
46
|
+
<span>
|
|
47
|
+
{selectedImportObject.retiredItemsCount} {t('conceptsRetired', 'concepts retired')}
|
|
48
|
+
</span>
|
|
49
|
+
<br />
|
|
50
|
+
<span>
|
|
51
|
+
{selectedImportObject.errorItemsCount} {t('errorsFound', 'errors found')}
|
|
52
|
+
</span>
|
|
53
|
+
<br />
|
|
54
|
+
<span>
|
|
55
|
+
{selectedImportObject.ignoredErrorsCount} {t('errorsIgnored', 'errors ignored')}
|
|
56
|
+
</span>
|
|
57
|
+
<br />
|
|
58
|
+
</div>
|
|
59
|
+
<br />
|
|
60
|
+
{selectedImportObject.errorItemsCount != 0 && <ImportItems importUuid={selectedImportObject.uuid} />}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default ImportOverview;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { formatDatetime } from '@openmrs/esm-framework';
|
|
4
|
+
import { renderWithSwr } from '../../../../../tools/test-helpers';
|
|
5
|
+
import { mockPreviousImports } from '../../../../../__mocks__/openconceptlab.mock';
|
|
6
|
+
import { Import } from '../../types';
|
|
7
|
+
import ImportOverview from './import-overview.component';
|
|
8
|
+
|
|
9
|
+
describe(`Import Overview component`, () => {
|
|
10
|
+
it(`renders without dying`, () => {
|
|
11
|
+
renderImportOverviewComponent(mockPreviousImports[0]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it(`renders the fields`, async () => {
|
|
15
|
+
renderImportOverviewComponent(mockPreviousImports[0]);
|
|
16
|
+
await waitForLoadingToFinish();
|
|
17
|
+
|
|
18
|
+
expect(screen.getByText('Started on')).toBeVisible();
|
|
19
|
+
expect(screen.getByText('Completed on')).toBeVisible();
|
|
20
|
+
expect(screen.getByText('Duration')).toBeVisible();
|
|
21
|
+
expect(screen.getByText('Result:')).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it(`renders the import details when no errored items`, async () => {
|
|
25
|
+
const selectedImport = mockPreviousImports[0];
|
|
26
|
+
|
|
27
|
+
renderImportOverviewComponent(selectedImport);
|
|
28
|
+
await waitForLoadingToFinish();
|
|
29
|
+
|
|
30
|
+
const startedTimeText = formatDatetime(selectedImport.localDateStarted);
|
|
31
|
+
const stoppedTimeText = formatDatetime(selectedImport.localDateStopped);
|
|
32
|
+
const upToDateItemsText = selectedImport.upToDateItemsCount + ' concepts up to date';
|
|
33
|
+
const addedItemsText = selectedImport.addedItemsCount + ' concepts added';
|
|
34
|
+
const updatedItemsText = selectedImport.updatedItemsCount + ' concepts updated';
|
|
35
|
+
const retiredItemsText = selectedImport.retiredItemsCount + ' concepts retired';
|
|
36
|
+
const errorItemsText = selectedImport.errorItemsCount + ' errors found';
|
|
37
|
+
const ignoredErrorsText = selectedImport.ignoredErrorsCount + ' errors ignored';
|
|
38
|
+
|
|
39
|
+
expect(screen.getByText(startedTimeText)).toBeVisible();
|
|
40
|
+
expect(screen.getByText(stoppedTimeText)).toBeVisible();
|
|
41
|
+
expect(screen.getByText(upToDateItemsText)).toBeVisible();
|
|
42
|
+
expect(screen.getByText(addedItemsText)).toBeVisible();
|
|
43
|
+
expect(screen.getByText(updatedItemsText)).toBeVisible();
|
|
44
|
+
expect(screen.getByText(retiredItemsText)).toBeVisible();
|
|
45
|
+
expect(screen.getByText(errorItemsText)).toBeVisible();
|
|
46
|
+
expect(screen.getByText(ignoredErrorsText)).toBeVisible();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it(`renders the import details when there are errored items`, async () => {
|
|
50
|
+
const selectedImport = mockPreviousImports[1];
|
|
51
|
+
|
|
52
|
+
renderImportOverviewComponent(selectedImport);
|
|
53
|
+
await waitForLoadingToFinish();
|
|
54
|
+
|
|
55
|
+
const startedTimeText = formatDatetime(selectedImport.localDateStarted);
|
|
56
|
+
const stoppedTimeText = formatDatetime(selectedImport.localDateStopped);
|
|
57
|
+
const upToDateItemsText = selectedImport.upToDateItemsCount + ' concepts up to date';
|
|
58
|
+
const addedItemsText = selectedImport.addedItemsCount + ' concepts added';
|
|
59
|
+
const updatedItemsText = selectedImport.updatedItemsCount + ' concepts updated';
|
|
60
|
+
const retiredItemsText = selectedImport.retiredItemsCount + ' concepts retired';
|
|
61
|
+
const errorItemsText = selectedImport.errorItemsCount + ' errors found';
|
|
62
|
+
const ignoredErrorsText = selectedImport.ignoredErrorsCount + ' errors ignored';
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText(startedTimeText)).toBeVisible();
|
|
65
|
+
expect(screen.getByText(stoppedTimeText)).toBeVisible();
|
|
66
|
+
expect(screen.getByText(upToDateItemsText)).toBeVisible();
|
|
67
|
+
expect(screen.getByText(addedItemsText)).toBeVisible();
|
|
68
|
+
expect(screen.getByText(updatedItemsText)).toBeVisible();
|
|
69
|
+
expect(screen.getByText(retiredItemsText)).toBeVisible();
|
|
70
|
+
expect(screen.getByText(errorItemsText)).toBeVisible();
|
|
71
|
+
expect(screen.getByText(ignoredErrorsText)).toBeVisible();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function renderImportOverviewComponent(importObject: Import) {
|
|
76
|
+
renderWithSwr(<ImportOverview selectedImportObject={importObject} />);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function waitForLoadingToFinish() {
|
|
80
|
+
return waitFor(() => {
|
|
81
|
+
expect(screen.getByText('Started on')).toBeVisible(), { timeout: 2000 };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use "@carbon/styles/scss/spacing";
|
|
2
|
+
@use "@carbon/styles/scss/type";
|
|
3
|
+
@import "~@openmrs/esm-styleguide/src/vars";
|
|
4
|
+
|
|
5
|
+
.grid {
|
|
6
|
+
margin-left: 0;
|
|
7
|
+
margin-right: 0;
|
|
8
|
+
padding-left: 0;
|
|
9
|
+
padding-right: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.productiveHeading03 {
|
|
13
|
+
@include type.type-style('productive-heading-03');
|
|
14
|
+
margin-bottom: spacing.$spacing-03;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.pagination {
|
|
18
|
+
@include type.type-style('body-short-01');
|
|
19
|
+
background-color: $ui-02;
|
|
20
|
+
color: $text-02;
|
|
21
|
+
border: 1px solid $ui-03;
|
|
22
|
+
display: flex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.tableBordered {
|
|
26
|
+
border: 1px solid $ui-03;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.tableRow > td {
|
|
30
|
+
background-color: $ui-02;
|
|
31
|
+
border-bottom: 1px solid $ui-03 !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.tableExpandedRow > td {
|
|
35
|
+
padding-left: spacing.$spacing-09 !important;
|
|
36
|
+
padding-right: spacing.$spacing-09;
|
|
37
|
+
padding-top: spacing.$spacing-05;
|
|
38
|
+
padding-bottom: spacing.$spacing-05;
|
|
39
|
+
}
|