@openmrs/esm-metadataexport-app 4.4.1-pre.809 → 4.4.1-pre.836

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.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/1118.css +1 -1
  3. package/dist/1118.css.map +1 -1
  4. package/dist/1118.js +1 -1
  5. package/dist/1118.js.map +1 -1
  6. package/dist/{4606.css.map → 2109.css.map} +1 -1
  7. package/dist/{4606.js → 2109.js} +10 -10
  8. package/dist/2109.js.map +1 -0
  9. package/dist/3566.js +1 -1
  10. package/dist/3566.js.map +1 -1
  11. package/dist/5065.js +1 -1
  12. package/dist/5065.js.map +1 -1
  13. package/dist/5117.js +1 -1
  14. package/dist/5117.js.map +1 -1
  15. package/dist/8359.js +1 -1
  16. package/dist/8359.js.map +1 -1
  17. package/dist/9503.js +1 -1
  18. package/dist/9503.js.map +1 -1
  19. package/dist/openmrs-esm-metadataexport-app.js +2 -2
  20. package/dist/openmrs-esm-metadataexport-app.js.buildmanifest.json +49 -49
  21. package/dist/routes.json +1 -1
  22. package/package.json +1 -1
  23. package/src/components/metadata-export-header/metadata-export-header.component.tsx +2 -2
  24. package/src/components/metadata-export-header/metadata-export-header.scss +1 -3
  25. package/src/components/metadata-export-header/metadata-export-header.test.tsx +6 -0
  26. package/src/components/metadata-package-form/metadata-package-form.workspace.test.tsx +1 -0
  27. package/src/components/metadata-package-form/metadata-package-form.workspace.tsx +2 -11
  28. package/src/components/packages-table/packages-table.component.tsx +57 -36
  29. package/src/components/packages-table/packages-table.scss +4 -0
  30. package/src/components/packages-table/packages-table.test.tsx +101 -5
  31. package/src/components/view-metadata-package/delete-metadata-package.modal.test.tsx +1 -0
  32. package/src/components/view-metadata-package/delete-metadata-package.modal.tsx +2 -5
  33. package/src/components/view-metadata-package/view-metadata-package.workspace.test.tsx +1 -0
  34. package/src/components/view-metadata-package/view-metadata-package.workspace.tsx +1 -5
  35. package/src/packages/packages.resource.ts +14 -7
  36. package/translations/en.json +2 -0
  37. package/translations/ru_RU.json +8 -8
  38. package/dist/4606.js.map +0 -1
  39. /package/dist/{4606.css → 2109.css} +0 -0
@@ -5,10 +5,10 @@
5
5
  "rendered": true,
6
6
  "initial": false,
7
7
  "entry": false,
8
- "size": 16217,
8
+ "size": 18197,
9
9
  "sizes": {
10
- "css/mini-extract": 1260,
11
- "javascript": 14957
10
+ "css/mini-extract": 1647,
11
+ "javascript": 16550
12
12
  },
13
13
  "names": [],
14
14
  "idHints": [],
@@ -23,7 +23,7 @@
23
23
  "1118.css.map",
24
24
  "1118.js.map"
25
25
  ],
26
- "hash": "26a442dd7b9cc1b5",
26
+ "hash": "432a533d06d633cd",
27
27
  "childrenByOrder": {}
28
28
  },
29
29
  {
@@ -116,7 +116,7 @@
116
116
  "auxiliaryFiles": [
117
117
  "openmrs-esm-metadataexport-app.js.map"
118
118
  ],
119
- "hash": "c1a40c759f22cc57",
119
+ "hash": "811132445a46d8f5",
120
120
  "childrenByOrder": {}
121
121
  },
122
122
  {
@@ -213,6 +213,35 @@
213
213
  "hash": "56a39d122616e3bb",
214
214
  "childrenByOrder": {}
215
215
  },
216
+ {
217
+ "type": "chunk",
218
+ "rendered": true,
219
+ "initial": false,
220
+ "entry": false,
221
+ "reason": "split chunk (cache group: defaultVendors)",
222
+ "size": 2664143,
223
+ "sizes": {
224
+ "css/mini-extract": 76447,
225
+ "javascript": 2587696
226
+ },
227
+ "names": [],
228
+ "idHints": [
229
+ "vendors"
230
+ ],
231
+ "runtime": [
232
+ "@openmrs/esm-metadataexport-app"
233
+ ],
234
+ "files": [
235
+ "2109.css",
236
+ "2109.js"
237
+ ],
238
+ "auxiliaryFiles": [
239
+ "2109.css.map",
240
+ "2109.js.map"
241
+ ],
242
+ "hash": "af52a569e62f98cf",
243
+ "childrenByOrder": {}
244
+ },
216
245
  {
217
246
  "type": "chunk",
218
247
  "rendered": true,
@@ -356,10 +385,10 @@
356
385
  "rendered": true,
357
386
  "initial": false,
358
387
  "entry": false,
359
- "size": 6758,
388
+ "size": 7102,
360
389
  "sizes": {
361
390
  "css/mini-extract": 90,
362
- "javascript": 6668
391
+ "javascript": 7012
363
392
  },
364
393
  "names": [],
365
394
  "idHints": [],
@@ -374,7 +403,7 @@
374
403
  "3566.css.map",
375
404
  "3566.js.map"
376
405
  ],
377
- "hash": "a670fcd76503c0c0",
406
+ "hash": "33eb2b6d605068dd",
378
407
  "childrenByOrder": {}
379
408
  },
380
409
  {
@@ -614,35 +643,6 @@
614
643
  "hash": "1aeb4abeb5edc0fb",
615
644
  "childrenByOrder": {}
616
645
  },
617
- {
618
- "type": "chunk",
619
- "rendered": true,
620
- "initial": false,
621
- "entry": false,
622
- "reason": "split chunk (cache group: defaultVendors)",
623
- "size": 2662671,
624
- "sizes": {
625
- "css/mini-extract": 76447,
626
- "javascript": 2586224
627
- },
628
- "names": [],
629
- "idHints": [
630
- "vendors"
631
- ],
632
- "runtime": [
633
- "@openmrs/esm-metadataexport-app"
634
- ],
635
- "files": [
636
- "4606.css",
637
- "4606.js"
638
- ],
639
- "auxiliaryFiles": [
640
- "4606.css.map",
641
- "4606.js.map"
642
- ],
643
- "hash": "0d4a67cd1a2c4215",
644
- "childrenByOrder": {}
645
- },
646
646
  {
647
647
  "type": "chunk",
648
648
  "rendered": true,
@@ -742,10 +742,10 @@
742
742
  "rendered": true,
743
743
  "initial": false,
744
744
  "entry": false,
745
- "size": 16942,
745
+ "size": 17286,
746
746
  "sizes": {
747
747
  "css/mini-extract": 1915,
748
- "javascript": 15027
748
+ "javascript": 15371
749
749
  },
750
750
  "names": [],
751
751
  "idHints": [],
@@ -760,7 +760,7 @@
760
760
  "5065.css.map",
761
761
  "5065.js.map"
762
762
  ],
763
- "hash": "f7a6feaeea1123ca",
763
+ "hash": "7c984b3d5f9664c5",
764
764
  "childrenByOrder": {}
765
765
  },
766
766
  {
@@ -768,9 +768,9 @@
768
768
  "rendered": true,
769
769
  "initial": false,
770
770
  "entry": false,
771
- "size": 2009,
771
+ "size": 2063,
772
772
  "sizes": {
773
- "javascript": 2009
773
+ "javascript": 2063
774
774
  },
775
775
  "names": [],
776
776
  "idHints": [],
@@ -783,7 +783,7 @@
783
783
  "auxiliaryFiles": [
784
784
  "5117.js.map"
785
785
  ],
786
- "hash": "ba78d6c1024322d4",
786
+ "hash": "d2addaad0ac571f2",
787
787
  "childrenByOrder": {}
788
788
  },
789
789
  {
@@ -1390,9 +1390,9 @@
1390
1390
  "rendered": true,
1391
1391
  "initial": false,
1392
1392
  "entry": false,
1393
- "size": 2553,
1393
+ "size": 2669,
1394
1394
  "sizes": {
1395
- "javascript": 2553
1395
+ "javascript": 2669
1396
1396
  },
1397
1397
  "names": [],
1398
1398
  "idHints": [],
@@ -1405,7 +1405,7 @@
1405
1405
  "auxiliaryFiles": [
1406
1406
  "8359.js.map"
1407
1407
  ],
1408
- "hash": "32ed2e68d1a30a4b",
1408
+ "hash": "f8149a32d2324963",
1409
1409
  "childrenByOrder": {}
1410
1410
  },
1411
1411
  {
@@ -1530,10 +1530,10 @@
1530
1530
  "rendered": true,
1531
1531
  "initial": false,
1532
1532
  "entry": false,
1533
- "size": 18800,
1533
+ "size": 19058,
1534
1534
  "sizes": {
1535
1535
  "css/mini-extract": 4201,
1536
- "javascript": 14599
1536
+ "javascript": 14857
1537
1537
  },
1538
1538
  "names": [],
1539
1539
  "idHints": [],
@@ -1548,7 +1548,7 @@
1548
1548
  "9503.css.map",
1549
1549
  "9503.js.map"
1550
1550
  ],
1551
- "hash": "bb461446daf5c0e1",
1551
+ "hash": "fccc11f218529838",
1552
1552
  "childrenByOrder": {}
1553
1553
  },
1554
1554
  {
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"metadataexport":">=1.0.0-SNAPSHOT"},"pages":[{"route":"metadataexport","component":"root","online":true}],"extensions":[{"name":"metadata-export-card-link","slot":"system-admin-page-card-link-slot","component":"metadataExportCardLink","privileges":["Get Metadata Export Packages"]}],"modals":[{"name":"delete-metadata-package-modal","component":"deleteMetadataPackageModal"}],"workspaces":[{"name":"metadata-package-form-workspace","component":"metadataPackageFormWorkspace","title":"New package","type":"form","canMaximize":true,"width":"narrow"},{"name":"view-metadata-package-workspace","component":"viewMetadataPackageWorkspace","title":"View package","type":"form","canMaximize":true,"width":"narrow"}],"version":"4.4.1-pre.809"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"metadataexport":">=1.0.0-SNAPSHOT"},"pages":[{"route":"metadataexport","component":"root","online":true}],"extensions":[{"name":"metadata-export-card-link","slot":"system-admin-page-card-link-slot","component":"metadataExportCardLink","privileges":["Get Metadata Export Packages"]}],"modals":[{"name":"delete-metadata-package-modal","component":"deleteMetadataPackageModal"}],"workspaces":[{"name":"metadata-package-form-workspace","component":"metadataPackageFormWorkspace","title":"New package","type":"form","canMaximize":true,"width":"narrow"},{"name":"view-metadata-package-workspace","component":"viewMetadataPackageWorkspace","title":"View package","type":"form","canMaximize":true,"width":"narrow"}],"version":"4.4.1-pre.836"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-metadataexport-app",
3
- "version": "4.4.1-pre.809",
3
+ "version": "4.4.1-pre.836",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Metadata export admin dashboard for O3",
6
6
  "browser": "dist/openmrs-esm-metadataexport-app.js",
@@ -1,12 +1,12 @@
1
1
  import React from 'react';
2
- import { PageHeader, PageHeaderContent } from '@openmrs/esm-framework';
2
+ import { PageHeader, PageHeaderContent, MetadataExportPictogram } from '@openmrs/esm-framework';
3
3
  import styles from './metadata-export-header.scss';
4
4
  import AddPackageActionButton from '../metadata-package-form/add-package-action-button/add-package-action-button.component';
5
5
 
6
6
  export const MetaDataExportHeader: React.FC<{ title: string }> = ({ title }) => {
7
7
  return (
8
8
  <PageHeader className={styles.header} data-testid="metadata-export-header">
9
- <PageHeaderContent illustration={<></>} title={title} />
9
+ <PageHeaderContent illustration={<MetadataExportPictogram />} title={title} />
10
10
 
11
11
  <div className={styles['right-justified-items']}>
12
12
  <AddPackageActionButton />
@@ -5,12 +5,10 @@
5
5
 
6
6
  .header {
7
7
  background-color: $ui-02;
8
- border: 1px solid $ui-03;
9
- border-left: 0;
8
+ border-block: 1px solid $ui-03;
10
9
  display: flex;
11
10
  justify-content: space-between;
12
11
  align-items: center;
13
- padding-left: layout.$spacing-06;
14
12
  padding-right: layout.$spacing-03;
15
13
  }
16
14
 
@@ -15,4 +15,10 @@ describe('MetaDataExportHeader', () => {
15
15
 
16
16
  expect(screen.getByTestId('metadata-export-header')).toBeInTheDocument();
17
17
  });
18
+
19
+ it('renders the metadata export pictogram', () => {
20
+ render(<MetaDataExportHeader title="Metadata export packages" />);
21
+
22
+ expect(screen.getByText('MetadataExportPictogram')).toBeInTheDocument();
23
+ });
18
24
  });
@@ -16,6 +16,7 @@ vi.mock('../../packages/packages.resource', () => ({
16
16
  createPackage: vi.fn(),
17
17
  editPackage: vi.fn(),
18
18
  usePackage: vi.fn(),
19
+ isPackagesCacheKey: vi.fn(),
19
20
  }));
20
21
 
21
22
  const mockUseDomains = useDomains as Mock;
@@ -13,20 +13,11 @@ import {
13
13
  TextInput,
14
14
  } from '@carbon/react';
15
15
  import { useSWRConfig } from 'swr';
16
- import {
17
- type DefaultWorkspaceProps,
18
- restBaseUrl,
19
- showSnackbar,
20
- useLayoutType,
21
- OpenmrsFetchError,
22
- } from '@openmrs/esm-framework';
16
+ import { type DefaultWorkspaceProps, showSnackbar, useLayoutType, OpenmrsFetchError } from '@openmrs/esm-framework';
23
17
  import { formatDomainLabel, useDomains } from '../../domain-lookups/domain-lookups.resource';
24
- import { createPackage, editPackage, usePackage } from '../../packages/packages.resource';
18
+ import { createPackage, editPackage, isPackagesCacheKey, usePackage } from '../../packages/packages.resource';
25
19
  import styles from './metadata-package-form.workspace.scss';
26
20
 
27
- const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
28
- const isPackagesCacheKey = (key: unknown) => typeof key === 'string' && key.startsWith(packagesUrl);
29
-
30
21
  interface PackageFormWorkspaceProps extends DefaultWorkspaceProps {
31
22
  uuid?: string;
32
23
  }
@@ -1,8 +1,9 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import {
4
4
  DataTable,
5
5
  DataTableSkeleton,
6
+ Pagination,
6
7
  Table,
7
8
  TableBody,
8
9
  TableCell,
@@ -21,7 +22,7 @@ import {
21
22
  userHasAccess,
22
23
  } from '@openmrs/esm-framework';
23
24
  import { formatDomainLabel } from '../../domain-lookups/domain-lookups.resource';
24
- import { useAllPackages } from '../../packages/packages.resource';
25
+ import { usePackages } from '../../packages/packages.resource';
25
26
  import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
26
27
  import ViewPackageActionButton from '../view-metadata-package/view-package-action-button/view-package-action-button.component';
27
28
  import styles from './packages-table.scss';
@@ -38,7 +39,8 @@ const PackagesTable: React.FC = () => {
38
39
  const { t } = useTranslation();
39
40
  const layout = useLayoutType();
40
41
  const session = useSession();
41
- const { packages, isLoading, error } = useAllPackages();
42
+ const [pageSize, setPageSize] = useState(10);
43
+ const { packages, totalCount, currentPage, goTo, isLoading, error } = usePackages(pageSize);
42
44
 
43
45
  const canManage = session.user ? userHasAccess('Manage Metadata Export Packages', session.user) : false;
44
46
 
@@ -75,7 +77,9 @@ const PackagesTable: React.FC = () => {
75
77
  [packages],
76
78
  );
77
79
 
78
- if (isLoading) {
80
+ // keepPreviousData holds the current page on screen while the next one loads, so only show the
81
+ // skeleton on the initial load; otherwise paging swaps the table for the skeleton and drops focus.
82
+ if (isLoading && !packages.length) {
79
83
  return (
80
84
  <div className={styles.container}>
81
85
  <DataTableSkeleton role="progressbar" columnCount={headers.length} zebra />
@@ -105,39 +109,56 @@ const PackagesTable: React.FC = () => {
105
109
 
106
110
  return (
107
111
  <div className={styles.container}>
108
- <CardHeader title={headerTitle} />
109
- <DataTable rows={rows} headers={headers} size={isDesktop(layout) ? 'sm' : 'lg'} useZebraStyles>
110
- {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
111
- <TableContainer>
112
- <Table {...getTableProps()}>
113
- <TableHead>
114
- <TableRow>
115
- {headers.map((header) => (
116
- <TableHeader {...getHeaderProps({ header })} key={header.key}>
117
- {header.header}
118
- </TableHeader>
119
- ))}
120
- </TableRow>
121
- </TableHead>
122
- <TableBody>
123
- {rows.map((row) => (
124
- <TableRow {...getRowProps({ row })} key={row.id}>
125
- {row.cells.map((cell) =>
126
- cell.info.header === 'actions' ? (
127
- <TableCell key={cell.id}>
128
- <ViewPackageActionButton exportPackage={packagesByUuid.get(row.id)} />
129
- </TableCell>
130
- ) : (
131
- <TableCell key={cell.id}>{cell.value}</TableCell>
132
- ),
133
- )}
112
+ <div className={styles.widgetCard}>
113
+ <CardHeader title={headerTitle} />
114
+ <DataTable rows={rows} headers={headers} size={isDesktop(layout) ? 'sm' : 'lg'} useZebraStyles>
115
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
116
+ <TableContainer>
117
+ <Table {...getTableProps()}>
118
+ <TableHead>
119
+ <TableRow>
120
+ {headers.map((header) => (
121
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
122
+ {header.header}
123
+ </TableHeader>
124
+ ))}
134
125
  </TableRow>
135
- ))}
136
- </TableBody>
137
- </Table>
138
- </TableContainer>
139
- )}
140
- </DataTable>
126
+ </TableHead>
127
+ <TableBody>
128
+ {rows.map((row) => (
129
+ <TableRow {...getRowProps({ row })} key={row.id}>
130
+ {row.cells.map((cell) =>
131
+ cell.info.header === 'actions' ? (
132
+ <TableCell key={cell.id}>
133
+ <ViewPackageActionButton exportPackage={packagesByUuid.get(row.id)} />
134
+ </TableCell>
135
+ ) : (
136
+ <TableCell key={cell.id}>{cell.value}</TableCell>
137
+ ),
138
+ )}
139
+ </TableRow>
140
+ ))}
141
+ </TableBody>
142
+ </Table>
143
+ </TableContainer>
144
+ )}
145
+ </DataTable>
146
+ <Pagination
147
+ forwardText={t('nextPage', 'Next page')}
148
+ backwardText={t('previousPage', 'Previous page')}
149
+ page={currentPage}
150
+ pageSize={pageSize}
151
+ pageSizes={[10, 20, 50, 100]}
152
+ totalItems={totalCount}
153
+ size={isDesktop(layout) ? 'sm' : 'lg'}
154
+ onChange={({ page, pageSize: newPageSize }) => {
155
+ if (newPageSize !== pageSize) {
156
+ setPageSize(newPageSize);
157
+ }
158
+ goTo(page);
159
+ }}
160
+ />
161
+ </div>
141
162
  </div>
142
163
  );
143
164
  };
@@ -1,5 +1,9 @@
1
1
  @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
2
3
 
3
4
  .container {
4
5
  margin: layout.$spacing-05;
5
6
  }
7
+ .widgetCard {
8
+ border: 1px solid $ui-03;
9
+ }
@@ -4,14 +4,14 @@ import userEvent from '@testing-library/user-event';
4
4
  import { type Session, useSession, userHasAccess } from '@openmrs/esm-framework';
5
5
  import { beforeEach, describe, expect, it, vi } from 'vitest';
6
6
  import { type ExportPackage } from '../../types/index';
7
- import { useAllPackages } from '../../packages/packages.resource';
7
+ import { usePackages } from '../../packages/packages.resource';
8
8
  import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
9
9
  import { launchViewMetadataPackageWorkspace } from '../view-metadata-package/view-metadata-package-utils';
10
10
  import PackagesTable from './packages-table.component';
11
11
 
12
12
  vi.mock('../../packages/packages.resource', async (importOriginal) => ({
13
13
  ...(await importOriginal<object>()),
14
- useAllPackages: vi.fn(),
14
+ usePackages: vi.fn(),
15
15
  }));
16
16
 
17
17
  vi.mock('../metadata-package-form/metadata-package-form-utils', () => ({
@@ -22,7 +22,7 @@ vi.mock('../view-metadata-package/view-metadata-package-utils', () => ({
22
22
  launchViewMetadataPackageWorkspace: vi.fn(),
23
23
  }));
24
24
 
25
- const mockUseAllPackages = vi.mocked(useAllPackages);
25
+ const mockUsePackages = vi.mocked(usePackages);
26
26
  const mockUseSession = vi.mocked(useSession);
27
27
  const mockUserHasAccess = vi.mocked(userHasAccess);
28
28
  const mockLaunchPackageFormWorkspace = vi.mocked(launchPackageFormWorkspace);
@@ -63,8 +63,12 @@ const unbuiltPackage: ExportPackage = {
63
63
 
64
64
  describe('PackagesTable', () => {
65
65
  beforeEach(() => {
66
- mockUseAllPackages.mockReturnValue({
66
+ mockUsePackages.mockReturnValue({
67
67
  packages: [],
68
+ totalCount: 0,
69
+ currentPage: 1,
70
+ currentPageSize: 10,
71
+ goTo: vi.fn(),
68
72
  isLoading: false,
69
73
  isValidating: false,
70
74
  error: undefined,
@@ -92,10 +96,50 @@ describe('PackagesTable', () => {
92
96
  expect(mockLaunchPackageFormWorkspace).not.toHaveBeenCalled();
93
97
  });
94
98
 
99
+ it('shows the loading skeleton on the initial load, before any packages have arrived', () => {
100
+ mockUsePackages.mockReturnValue({
101
+ packages: [],
102
+ totalCount: 0,
103
+ currentPage: 1,
104
+ currentPageSize: 10,
105
+ goTo: vi.fn(),
106
+ isLoading: true,
107
+ isValidating: false,
108
+ error: undefined,
109
+ mutate: vi.fn(),
110
+ });
111
+ render(<PackagesTable />);
112
+
113
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
114
+ });
115
+
116
+ it('keeps the current page on screen while the next page loads instead of swapping in the skeleton', () => {
117
+ // keepPreviousData means isLoading can be true while the previous page's data is still present.
118
+ mockUsePackages.mockReturnValue({
119
+ packages: [builtPackage, unbuiltPackage],
120
+ totalCount: 25,
121
+ currentPage: 1,
122
+ currentPageSize: 10,
123
+ goTo: vi.fn(),
124
+ isLoading: true,
125
+ isValidating: false,
126
+ error: undefined,
127
+ mutate: vi.fn(),
128
+ });
129
+ render(<PackagesTable />);
130
+
131
+ expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
132
+ expect(screen.getByRole('row', { name: /Core reference data/ })).toBeInTheDocument();
133
+ });
134
+
95
135
  it('renders the build status per package and launches the view workspace with the package for the clicked row', async () => {
96
136
  const user = userEvent.setup();
97
- mockUseAllPackages.mockReturnValue({
137
+ mockUsePackages.mockReturnValue({
98
138
  packages: [builtPackage, unbuiltPackage],
139
+ totalCount: 2,
140
+ currentPage: 1,
141
+ currentPageSize: 10,
142
+ goTo: vi.fn(),
99
143
  isLoading: false,
100
144
  isValidating: false,
101
145
  error: undefined,
@@ -123,4 +167,56 @@ describe('PackagesTable', () => {
123
167
 
124
168
  expect(mockLaunchViewPackageWorkspace).toHaveBeenCalledTimes(2);
125
169
  });
170
+
171
+ // 25 packages across pages of 10 gives three pages, so both Next and the page-size selector are active.
172
+ const firstPageOfMany = Array.from({ length: 10 }, (_, index) => ({
173
+ ...unbuiltPackage,
174
+ uuid: `a1b2c3d4-0000-0000-0000-00000000${(index + 10).toString().padStart(4, '0')}`,
175
+ name: `Package ${index + 1}`,
176
+ }));
177
+
178
+ it('advances to the next page when the "Next page" control is clicked', async () => {
179
+ const user = userEvent.setup();
180
+ const goTo = vi.fn();
181
+ mockUsePackages.mockReturnValue({
182
+ packages: firstPageOfMany,
183
+ totalCount: 25,
184
+ currentPage: 1,
185
+ currentPageSize: 10,
186
+ goTo,
187
+ isLoading: false,
188
+ isValidating: false,
189
+ error: undefined,
190
+ mutate: vi.fn(),
191
+ });
192
+ render(<PackagesTable />);
193
+
194
+ await user.click(screen.getByRole('button', { name: 'Next page' }));
195
+
196
+ expect(goTo).toHaveBeenCalledWith(2);
197
+ });
198
+
199
+ it('refetches with the new page size and resets to the first page when the page size changes', async () => {
200
+ const user = userEvent.setup();
201
+ const goTo = vi.fn();
202
+ mockUsePackages.mockReturnValue({
203
+ packages: firstPageOfMany,
204
+ totalCount: 25,
205
+ currentPage: 1,
206
+ currentPageSize: 10,
207
+ goTo,
208
+ isLoading: false,
209
+ isValidating: false,
210
+ error: undefined,
211
+ mutate: vi.fn(),
212
+ });
213
+ render(<PackagesTable />);
214
+
215
+ expect(mockUsePackages).toHaveBeenCalledWith(10);
216
+
217
+ await user.selectOptions(screen.getByLabelText(/items per page/i), '20');
218
+
219
+ expect(mockUsePackages).toHaveBeenCalledWith(20);
220
+ expect(goTo).toHaveBeenCalledWith(1);
221
+ });
126
222
  });
@@ -9,6 +9,7 @@ import type { ExportPackage } from '../../types';
9
9
 
10
10
  vi.mock('../../packages/packages.resource', () => ({
11
11
  deletePackage: vi.fn(),
12
+ isPackagesCacheKey: vi.fn(),
12
13
  }));
13
14
 
14
15
  const mockDeletePackage = deletePackage as Mock;
@@ -2,14 +2,11 @@ import React, { useCallback, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader, TextArea } from '@carbon/react';
4
4
  import { useSWRConfig } from 'swr';
5
- import { OpenmrsFetchError, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
6
- import { deletePackage } from '../../packages/packages.resource';
5
+ import { OpenmrsFetchError, showSnackbar } from '@openmrs/esm-framework';
6
+ import { deletePackage, isPackagesCacheKey } from '../../packages/packages.resource';
7
7
  import type { ExportPackage } from '../../types';
8
8
  import styles from './delete-metadata-package.modal.scss';
9
9
 
10
- const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
11
- const isPackagesCacheKey = (key: unknown) => typeof key === 'string' && key.startsWith(packagesUrl);
12
-
13
10
  interface DeletePackageModalProps {
14
11
  closeModal: () => void;
15
12
  exportPackage: ExportPackage;
@@ -20,6 +20,7 @@ vi.mock('@openmrs/esm-framework', async (importOriginal) => {
20
20
  vi.mock('../../packages/packages.resource', () => ({
21
21
  usePackageBuilds: vi.fn(),
22
22
  triggerBuild: vi.fn(),
23
+ isPackagesCacheKey: vi.fn(),
23
24
  }));
24
25
 
25
26
  const mockTriggerBuild = triggerBuild as Mock;
@@ -16,15 +16,11 @@ import {
16
16
  userHasAccess,
17
17
  } from '@openmrs/esm-framework';
18
18
  import { formatDomainLabel } from '../../domain-lookups/domain-lookups.resource';
19
- import { triggerBuild, usePackageBuilds } from '../../packages/packages.resource';
19
+ import { isPackagesCacheKey, triggerBuild, usePackageBuilds } from '../../packages/packages.resource';
20
20
  import type { ExportBuildStatus, ExportPackage } from '../../types';
21
21
  import styles from './view-metadata-package.workspace.scss';
22
22
  import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
23
23
 
24
- const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
25
- // useOpenmrsPagination keys the cache with absolute URLs, so match on inclusion rather than prefix.
26
- const isPackagesCacheKey = (key: unknown) => typeof key === 'string' && key.includes(packagesUrl);
27
-
28
24
  interface ViewPackageWorkspaceProps extends DefaultWorkspaceProps {
29
25
  exportPackage: ExportPackage;
30
26
  }