@openmrs/esm-metadataexport-app 4.4.1-pre.799 → 4.4.1-pre.805
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 +1 -1
- package/dist/{5739.css.map → 1118.css.map} +1 -1
- package/dist/1118.js +1 -0
- package/dist/1118.js.map +1 -0
- package/dist/3566.css +1 -0
- package/dist/3566.css.map +1 -0
- package/dist/3566.js +1 -0
- package/dist/3566.js.map +1 -0
- package/dist/4606.js +1 -1
- package/dist/4606.js.map +1 -1
- package/dist/5065.css +1 -0
- package/dist/5065.css.map +1 -0
- package/dist/5065.js +1 -0
- package/dist/5065.js.map +1 -0
- package/dist/5117.js +1 -1
- package/dist/5117.js.map +1 -1
- package/dist/6813.js +1 -1
- package/dist/6813.js.map +1 -1
- package/dist/9503.css +1 -0
- package/dist/9503.css.map +1 -0
- package/dist/9503.js +1 -0
- package/dist/9503.js.map +1 -0
- package/dist/openmrs-esm-metadataexport-app.js +2 -2
- package/dist/openmrs-esm-metadataexport-app.js.buildmanifest.json +95 -95
- package/dist/openmrs-esm-metadataexport-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/metadata-export-header/metadata-export-header.component.tsx +2 -2
- package/src/components/{new-package → metadata-package-form}/add-package-action-button/add-package-action-button.component.tsx +6 -6
- package/src/components/{new-package → metadata-package-form}/add-package-action-button/add-package-action-button.test.tsx +12 -12
- package/src/components/metadata-package-form/metadata-package-form-utils.ts +9 -0
- package/src/components/{new-package/new-package.workspace.test.tsx → metadata-package-form/metadata-package-form.workspace.test.tsx} +147 -6
- package/src/components/{new-package/new-package.workspace.tsx → metadata-package-form/metadata-package-form.workspace.tsx} +111 -22
- package/src/components/packages-table/packages-table.component.tsx +3 -3
- package/src/components/packages-table/packages-table.test.tsx +10 -10
- package/src/components/{view-package/delete-package.modal.test.tsx → view-metadata-package/delete-metadata-package.modal.test.tsx} +4 -2
- package/src/components/{view-package/delete-package.modal.tsx → view-metadata-package/delete-metadata-package.modal.tsx} +3 -3
- package/src/components/{view-package/view-package-utils.ts → view-metadata-package/view-metadata-package-utils.ts} +2 -2
- package/src/components/{view-package/view-package.workspace.scss → view-metadata-package/view-metadata-package.workspace.scss} +14 -2
- package/src/components/{view-package/view-package.workspace.test.tsx → view-metadata-package/view-metadata-package.workspace.test.tsx} +52 -2
- package/src/components/{view-package/view-package.workspace.tsx → view-metadata-package/view-metadata-package.workspace.tsx} +28 -7
- package/src/components/{view-package → view-metadata-package}/view-package-action-button/view-package-action-button.component.test.tsx +4 -4
- package/src/components/{view-package → view-metadata-package}/view-package-action-button/view-package-action-button.component.tsx +2 -2
- package/src/index.ts +6 -6
- package/src/packages/packages.resource.ts +42 -19
- package/src/routes.json +6 -6
- package/translations/en.json +8 -0
- package/dist/4974.css +0 -1
- package/dist/4974.css.map +0 -1
- package/dist/4974.js +0 -1
- package/dist/4974.js.map +0 -1
- package/dist/5309.css +0 -1
- package/dist/5309.css.map +0 -1
- package/dist/5309.js +0 -1
- package/dist/5309.js.map +0 -1
- package/dist/5739.js +0 -1
- package/dist/5739.js.map +0 -1
- package/dist/827.css +0 -1
- package/dist/827.css.map +0 -1
- package/dist/827.js +0 -1
- package/dist/827.js.map +0 -1
- package/src/components/new-package/new-package-utils.ts +0 -8
- /package/dist/{5739.css → 1118.css} +0 -0
- /package/src/components/{new-package/new-package.workspace.scss → metadata-package-form/metadata-package-form.workspace.scss} +0 -0
- /package/src/components/{view-package/delete-package.modal.scss → view-metadata-package/delete-metadata-package.modal.scss} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
@@ -21,13 +21,18 @@ import {
|
|
|
21
21
|
OpenmrsFetchError,
|
|
22
22
|
} from '@openmrs/esm-framework';
|
|
23
23
|
import { formatDomainLabel, useDomains } from '../../domain-lookups/domain-lookups.resource';
|
|
24
|
-
import { createPackage } from '../../packages/packages.resource';
|
|
25
|
-
import styles from './
|
|
24
|
+
import { createPackage, editPackage, usePackage } from '../../packages/packages.resource';
|
|
25
|
+
import styles from './metadata-package-form.workspace.scss';
|
|
26
26
|
|
|
27
27
|
const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
|
|
28
28
|
const isPackagesCacheKey = (key: unknown) => typeof key === 'string' && key.startsWith(packagesUrl);
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
interface PackageFormWorkspaceProps extends DefaultWorkspaceProps {
|
|
31
|
+
uuid?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const MetadataPackageFormWorkspace: React.FC<PackageFormWorkspaceProps> = ({
|
|
35
|
+
uuid,
|
|
31
36
|
closeWorkspace,
|
|
32
37
|
closeWorkspaceWithSavedChanges,
|
|
33
38
|
promptBeforeClosing,
|
|
@@ -35,6 +40,7 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
35
40
|
const { t } = useTranslation();
|
|
36
41
|
const { mutate } = useSWRConfig();
|
|
37
42
|
const isTablet = useLayoutType() === 'tablet';
|
|
43
|
+
const isEditMode = Boolean(uuid);
|
|
38
44
|
const { domains, isLoading, error } = useDomains();
|
|
39
45
|
|
|
40
46
|
const [packageName, setPackageName] = useState('');
|
|
@@ -45,6 +51,8 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
45
51
|
const allSelected = domains.length > 0 && selectedDomains.size === domains.length;
|
|
46
52
|
const someSelected = selectedDomains.size > 0 && !allSelected;
|
|
47
53
|
|
|
54
|
+
const { exportPackage, isLoading: isLoadingPackage, error: packageError } = usePackage(uuid ?? '');
|
|
55
|
+
|
|
48
56
|
const toggleSelectAll = useCallback(
|
|
49
57
|
(checked: boolean) => {
|
|
50
58
|
setSelectedDomains(checked ? new Set(domains) : new Set());
|
|
@@ -70,6 +78,28 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
70
78
|
promptBeforeClosing(() => hasUnsavedChanges);
|
|
71
79
|
}, [hasUnsavedChanges, promptBeforeClosing]);
|
|
72
80
|
|
|
81
|
+
// Seed the form from the fetched package exactly once, so a later SWR revalidation
|
|
82
|
+
// of the package or domain list doesn't clobber the user's in-progress edits.
|
|
83
|
+
// An empty entries list means the package includes every registered domain.
|
|
84
|
+
const hasPrefilledFromPackage = useRef(false);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!isEditMode || hasPrefilledFromPackage.current || !exportPackage) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Wait for the domain list before expanding an "all domains" package.
|
|
90
|
+
if (exportPackage.entries.length === 0 && domains.length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
hasPrefilledFromPackage.current = true;
|
|
94
|
+
setPackageName(exportPackage.name);
|
|
95
|
+
setDescription(exportPackage.description ?? '');
|
|
96
|
+
setSelectedDomains(
|
|
97
|
+
exportPackage.entries.length === 0
|
|
98
|
+
? new Set(domains)
|
|
99
|
+
: new Set(exportPackage.entries.map((entry) => entry.domain)),
|
|
100
|
+
);
|
|
101
|
+
}, [isEditMode, exportPackage, domains]);
|
|
102
|
+
|
|
73
103
|
const handleSubmit = useCallback(
|
|
74
104
|
async (event: React.FormEvent<HTMLFormElement>) => {
|
|
75
105
|
event.preventDefault();
|
|
@@ -77,17 +107,32 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
77
107
|
|
|
78
108
|
// An explicit empty array tells the server to include every registered domain.
|
|
79
109
|
const entries = allSelected ? [] : Array.from(selectedDomains, (domain) => ({ domain }));
|
|
110
|
+
const payload = { name: packageName.trim(), description: description.trim(), entries };
|
|
80
111
|
|
|
81
112
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
const name = packageName.trim();
|
|
114
|
+
|
|
115
|
+
if (isEditMode && uuid) {
|
|
116
|
+
await editPackage(uuid, payload);
|
|
117
|
+
// Revalidate the packages list so the table reflects the change without a refresh.
|
|
118
|
+
await mutate(isPackagesCacheKey);
|
|
119
|
+
showSnackbar({
|
|
120
|
+
title: t('packageUpdated', 'Package updated'),
|
|
121
|
+
subtitle: t('packageUpdatedSubtitle', '{{name}} was updated successfully', { name }),
|
|
122
|
+
kind: 'success',
|
|
123
|
+
isLowContrast: true,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
await createPackage(payload);
|
|
127
|
+
// Revalidate the packages list so the table reflects the change without a refresh.
|
|
128
|
+
await mutate(isPackagesCacheKey);
|
|
129
|
+
showSnackbar({
|
|
130
|
+
title: t('packageCreated', 'Package created'),
|
|
131
|
+
subtitle: t('packageCreatedSubtitle', '{{name}} was created successfully', { name }),
|
|
132
|
+
kind: 'success',
|
|
133
|
+
isLowContrast: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
91
136
|
// Bypass the "unsaved changes" prompt now that the package is persisted.
|
|
92
137
|
closeWorkspaceWithSavedChanges();
|
|
93
138
|
} catch (submitError) {
|
|
@@ -96,21 +141,63 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
96
141
|
typeof responseBody === 'object' && responseBody !== null
|
|
97
142
|
? Object.values(responseBody.fieldErrors ?? {})[0] ?? responseBody.error
|
|
98
143
|
: null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
const subtitle = reason ?? t('unexpectedError', 'An unexpected error occurred');
|
|
145
|
+
|
|
146
|
+
if (isEditMode) {
|
|
147
|
+
showSnackbar({
|
|
148
|
+
title: t('packageUpdateFailed', 'Failed to update package'),
|
|
149
|
+
subtitle,
|
|
150
|
+
kind: 'error',
|
|
151
|
+
isLowContrast: false,
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
showSnackbar({
|
|
155
|
+
title: t('packageCreationFailed', 'Failed to create package'),
|
|
156
|
+
subtitle,
|
|
157
|
+
kind: 'error',
|
|
158
|
+
isLowContrast: false,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
105
161
|
} finally {
|
|
106
162
|
setIsSubmitting(false);
|
|
107
163
|
}
|
|
108
164
|
},
|
|
109
|
-
[
|
|
165
|
+
[
|
|
166
|
+
allSelected,
|
|
167
|
+
closeWorkspaceWithSavedChanges,
|
|
168
|
+
description,
|
|
169
|
+
isEditMode,
|
|
170
|
+
mutate,
|
|
171
|
+
packageName,
|
|
172
|
+
selectedDomains,
|
|
173
|
+
t,
|
|
174
|
+
uuid,
|
|
175
|
+
],
|
|
110
176
|
);
|
|
111
177
|
|
|
112
178
|
const isSubmitDisabled = packageName.trim().length === 0 || selectedDomains.size === 0 || isSubmitting;
|
|
113
179
|
|
|
180
|
+
if (isEditMode && isLoadingPackage) {
|
|
181
|
+
return (
|
|
182
|
+
<div className={styles.form}>
|
|
183
|
+
<InlineLoading description={t('loadingPackage', 'Loading package…')} />
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (packageError) {
|
|
189
|
+
return (
|
|
190
|
+
<div className={styles.form}>
|
|
191
|
+
<InlineNotification
|
|
192
|
+
kind="error"
|
|
193
|
+
lowContrast
|
|
194
|
+
title={t('errorLoadingPackage', 'Error loading package')}
|
|
195
|
+
subtitle={packageError.message}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
114
201
|
return (
|
|
115
202
|
<Form className={styles.form} onSubmit={handleSubmit}>
|
|
116
203
|
<div className={styles.grid}>
|
|
@@ -184,7 +271,9 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
184
271
|
</Button>
|
|
185
272
|
<Button className={styles.button} kind="primary" type="submit" disabled={isSubmitDisabled}>
|
|
186
273
|
{isSubmitting ? (
|
|
187
|
-
<InlineLoading description={t('creating', 'Creating') + '…'} />
|
|
274
|
+
<InlineLoading description={(isEditMode ? t('saving', 'Saving') : t('creating', 'Creating')) + '…'} />
|
|
275
|
+
) : isEditMode ? (
|
|
276
|
+
t('saveChanges', 'Save changes')
|
|
188
277
|
) : (
|
|
189
278
|
t('createPackage', 'Create package')
|
|
190
279
|
)}
|
|
@@ -194,4 +283,4 @@ const NewPackageWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
|
194
283
|
);
|
|
195
284
|
};
|
|
196
285
|
|
|
197
|
-
export default
|
|
286
|
+
export default MetadataPackageFormWorkspace;
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
} from '@openmrs/esm-framework';
|
|
23
23
|
import { formatDomainLabel } from '../../domain-lookups/domain-lookups.resource';
|
|
24
24
|
import { useAllPackages } from '../../packages/packages.resource';
|
|
25
|
-
import {
|
|
26
|
-
import ViewPackageActionButton from '../view-package/view-package-action-button/view-package-action-button.component';
|
|
25
|
+
import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
|
|
26
|
+
import ViewPackageActionButton from '../view-metadata-package/view-package-action-button/view-package-action-button.component';
|
|
27
27
|
import styles from './packages-table.scss';
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -97,7 +97,7 @@ const PackagesTable: React.FC = () => {
|
|
|
97
97
|
<EmptyCard
|
|
98
98
|
displayText={t('packages__lower', 'packages')}
|
|
99
99
|
headerTitle={headerTitle}
|
|
100
|
-
launchForm={canManage ? () =>
|
|
100
|
+
launchForm={canManage ? () => launchPackageFormWorkspace(t) : undefined}
|
|
101
101
|
/>
|
|
102
102
|
</div>
|
|
103
103
|
);
|
|
@@ -5,8 +5,8 @@ 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
7
|
import { useAllPackages } from '../../packages/packages.resource';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
|
|
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) => ({
|
|
@@ -14,19 +14,19 @@ vi.mock('../../packages/packages.resource', async (importOriginal) => ({
|
|
|
14
14
|
useAllPackages: vi.fn(),
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
-
vi.mock('../
|
|
18
|
-
|
|
17
|
+
vi.mock('../metadata-package-form/metadata-package-form-utils', () => ({
|
|
18
|
+
launchPackageFormWorkspace: vi.fn(),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
vi.mock('../view-package/view-package-utils', () => ({
|
|
22
|
-
|
|
21
|
+
vi.mock('../view-metadata-package/view-metadata-package-utils', () => ({
|
|
22
|
+
launchViewMetadataPackageWorkspace: vi.fn(),
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
25
|
const mockUseAllPackages = vi.mocked(useAllPackages);
|
|
26
26
|
const mockUseSession = vi.mocked(useSession);
|
|
27
27
|
const mockUserHasAccess = vi.mocked(userHasAccess);
|
|
28
|
-
const
|
|
29
|
-
const mockLaunchViewPackageWorkspace = vi.mocked(
|
|
28
|
+
const mockLaunchPackageFormWorkspace = vi.mocked(launchPackageFormWorkspace);
|
|
29
|
+
const mockLaunchViewPackageWorkspace = vi.mocked(launchViewMetadataPackageWorkspace);
|
|
30
30
|
|
|
31
31
|
const sessionWithUser = () => ({ user: { uuid: 'cc8507b8-7c9a-486b-85dc-b8f25ad1e4cc' } }) as unknown as Session;
|
|
32
32
|
|
|
@@ -81,7 +81,7 @@ describe('PackagesTable', () => {
|
|
|
81
81
|
await user.click(screen.getByRole('button', { name: 'Record packages' }));
|
|
82
82
|
|
|
83
83
|
expect(mockUserHasAccess).toHaveBeenCalledWith('Manage Metadata Export Packages', expect.anything());
|
|
84
|
-
expect(
|
|
84
|
+
expect(mockLaunchPackageFormWorkspace).toHaveBeenCalledTimes(1);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
it('does not offer the empty-state action to users without the manage privilege', () => {
|
|
@@ -89,7 +89,7 @@ describe('PackagesTable', () => {
|
|
|
89
89
|
render(<PackagesTable />);
|
|
90
90
|
|
|
91
91
|
expect(screen.queryByRole('button', { name: 'Record packages' })).not.toBeInTheDocument();
|
|
92
|
-
expect(
|
|
92
|
+
expect(mockLaunchPackageFormWorkspace).not.toHaveBeenCalled();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
it('renders the build status per package and launches the view workspace with the package for the clicked row', async () => {
|
|
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
|
5
5
|
import * as esmFramework from '@openmrs/esm-framework';
|
|
6
|
-
import
|
|
6
|
+
import DeleteMetadataPackageModal from './delete-metadata-package.modal';
|
|
7
7
|
import { deletePackage } from '../../packages/packages.resource';
|
|
8
8
|
import type { ExportPackage } from '../../types';
|
|
9
9
|
|
|
@@ -28,7 +28,9 @@ const mockCloseModal = vi.fn();
|
|
|
28
28
|
const mockOnDeleted = vi.fn();
|
|
29
29
|
|
|
30
30
|
function renderModal() {
|
|
31
|
-
render(
|
|
31
|
+
render(
|
|
32
|
+
<DeleteMetadataPackageModal closeModal={mockCloseModal} exportPackage={exportPackage} onDeleted={mockOnDeleted} />,
|
|
33
|
+
);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
describe('DeletePackageModal', () => {
|
|
@@ -5,7 +5,7 @@ import { useSWRConfig } from 'swr';
|
|
|
5
5
|
import { OpenmrsFetchError, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
|
|
6
6
|
import { deletePackage } from '../../packages/packages.resource';
|
|
7
7
|
import type { ExportPackage } from '../../types';
|
|
8
|
-
import styles from './delete-package.modal.scss';
|
|
8
|
+
import styles from './delete-metadata-package.modal.scss';
|
|
9
9
|
|
|
10
10
|
const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
|
|
11
11
|
const isPackagesCacheKey = (key: unknown) => typeof key === 'string' && key.startsWith(packagesUrl);
|
|
@@ -16,7 +16,7 @@ interface DeletePackageModalProps {
|
|
|
16
16
|
onDeleted: () => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const DeleteMetadataPackageModal: React.FC<DeletePackageModalProps> = ({ closeModal, exportPackage, onDeleted }) => {
|
|
20
20
|
const { t } = useTranslation();
|
|
21
21
|
const { mutate } = useSWRConfig();
|
|
22
22
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
@@ -79,4 +79,4 @@ const DeletePackageModal: React.FC<DeletePackageModalProps> = ({ closeModal, exp
|
|
|
79
79
|
);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
export default
|
|
82
|
+
export default DeleteMetadataPackageModal;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
2
2
|
import { type ExportPackage } from '../../types/index';
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
launchWorkspace('view-package-workspace', {
|
|
4
|
+
export const launchViewMetadataPackageWorkspace = (exportPackage: ExportPackage) => {
|
|
5
|
+
launchWorkspace('view-metadata-package-workspace', {
|
|
6
6
|
workspaceTitle: exportPackage.name,
|
|
7
7
|
exportPackage,
|
|
8
8
|
});
|
|
@@ -25,12 +25,24 @@
|
|
|
25
25
|
|
|
26
26
|
.actionRow {
|
|
27
27
|
display: flex;
|
|
28
|
-
flex-direction:
|
|
28
|
+
flex-direction: column;
|
|
29
29
|
gap: layout.$spacing-04;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.triggerButton {
|
|
33
|
-
|
|
33
|
+
width: 100%;
|
|
34
|
+
max-width: none;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.secondaryActions {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: row;
|
|
41
|
+
gap: layout.$spacing-04;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.secondaryButton {
|
|
45
|
+
flex: 1 1 0;
|
|
34
46
|
max-width: none;
|
|
35
47
|
justify-content: center;
|
|
36
48
|
}
|
|
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
|
5
5
|
import * as esmFramework from '@openmrs/esm-framework';
|
|
6
|
-
import
|
|
6
|
+
import ViewMetadataPackageWorkspace from './view-metadata-package.workspace';
|
|
7
7
|
import { usePackageBuilds, triggerBuild } from '../../packages/packages.resource';
|
|
8
8
|
import type { ExportPackage, ExportPackageBuild } from '../../types';
|
|
9
9
|
|
|
@@ -75,7 +75,7 @@ function mockBuilds(overrides: Partial<ReturnType<typeof usePackageBuilds>> = {}
|
|
|
75
75
|
|
|
76
76
|
function renderWorkspace(pkg: ExportPackage = exportPackage) {
|
|
77
77
|
render(
|
|
78
|
-
<
|
|
78
|
+
<ViewMetadataPackageWorkspace
|
|
79
79
|
exportPackage={pkg}
|
|
80
80
|
closeWorkspace={mockCloseWorkspace}
|
|
81
81
|
closeWorkspaceWithSavedChanges={mockCloseWorkspaceWithSavedChanges}
|
|
@@ -224,4 +224,54 @@ describe('ViewPackageWorkspace', () => {
|
|
|
224
224
|
}),
|
|
225
225
|
);
|
|
226
226
|
});
|
|
227
|
+
|
|
228
|
+
it('triggers a build and revalidates the builds list', async () => {
|
|
229
|
+
const user = userEvent.setup();
|
|
230
|
+
const mutate = vi.fn();
|
|
231
|
+
mockBuilds({ mutate });
|
|
232
|
+
mockTriggerBuild.mockResolvedValue({});
|
|
233
|
+
renderWorkspace();
|
|
234
|
+
|
|
235
|
+
await user.click(screen.getByRole('button', { name: 'Trigger new build' }));
|
|
236
|
+
|
|
237
|
+
expect(mockTriggerBuild).toHaveBeenCalledWith(exportPackage.uuid);
|
|
238
|
+
await waitFor(() => expect(mutate).toHaveBeenCalled());
|
|
239
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('shows an error snackbar when triggering a build fails', async () => {
|
|
243
|
+
const user = userEvent.setup();
|
|
244
|
+
const error = new esmFramework.OpenmrsFetchError(
|
|
245
|
+
'/url',
|
|
246
|
+
{} as Response,
|
|
247
|
+
{ error: 'A build is already running for this package' },
|
|
248
|
+
new Error(),
|
|
249
|
+
);
|
|
250
|
+
mockTriggerBuild.mockRejectedValue(error);
|
|
251
|
+
renderWorkspace();
|
|
252
|
+
|
|
253
|
+
await user.click(screen.getByRole('button', { name: 'Trigger new build' }));
|
|
254
|
+
|
|
255
|
+
await waitFor(() =>
|
|
256
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
257
|
+
expect.objectContaining({ kind: 'error', subtitle: 'A build is already running for this package' }),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('launches the delete confirmation modal wired to close the workspace on success', async () => {
|
|
263
|
+
const user = userEvent.setup();
|
|
264
|
+
renderWorkspace();
|
|
265
|
+
|
|
266
|
+
await user.click(screen.getByRole('button', { name: /delete/i }));
|
|
267
|
+
|
|
268
|
+
expect(mockShowModal).toHaveBeenCalledWith(
|
|
269
|
+
'delete-package-modal',
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
exportPackage,
|
|
272
|
+
onDeleted: mockCloseWorkspace,
|
|
273
|
+
closeModal: expect.any(Function),
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
});
|
|
227
277
|
});
|
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
import { formatDomainLabel } from '../../domain-lookups/domain-lookups.resource';
|
|
19
19
|
import { triggerBuild, usePackageBuilds } from '../../packages/packages.resource';
|
|
20
20
|
import type { ExportBuildStatus, ExportPackage } from '../../types';
|
|
21
|
-
import styles from './view-package.workspace.scss';
|
|
21
|
+
import styles from './view-metadata-package.workspace.scss';
|
|
22
|
+
import { launchPackageFormWorkspace } from '../metadata-package-form/metadata-package-form-utils';
|
|
22
23
|
|
|
23
24
|
const packagesUrl = `${restBaseUrl}/metadataexport/packages`;
|
|
24
|
-
|
|
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);
|
|
25
27
|
|
|
26
28
|
interface ViewPackageWorkspaceProps extends DefaultWorkspaceProps {
|
|
27
29
|
exportPackage: ExportPackage;
|
|
@@ -34,7 +36,7 @@ const statusTagType: Record<ExportBuildStatus, 'gray' | 'blue' | 'green' | 'red'
|
|
|
34
36
|
FAILED: 'red',
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
const
|
|
39
|
+
const ViewMetadataPackageWorkspace: React.FC<ViewPackageWorkspaceProps> = ({ exportPackage, closeWorkspace }) => {
|
|
38
40
|
const { t } = useTranslation();
|
|
39
41
|
const session = useSession();
|
|
40
42
|
const { mutate: globalMutate } = useSWRConfig();
|
|
@@ -102,6 +104,10 @@ const ViewPackageWorkspace: React.FC<ViewPackageWorkspaceProps> = ({ exportPacka
|
|
|
102
104
|
return exportPackage.entries.map((entry) => formatDomainLabel(entry.domain)).join(', ');
|
|
103
105
|
}, [exportPackage.entries, t]);
|
|
104
106
|
|
|
107
|
+
const editPackage = useCallback(() => {
|
|
108
|
+
launchPackageFormWorkspace(t, exportPackage.uuid);
|
|
109
|
+
}, [exportPackage.uuid, t]);
|
|
110
|
+
|
|
105
111
|
return (
|
|
106
112
|
<div className={styles.container}>
|
|
107
113
|
<div className={styles.body}>
|
|
@@ -119,9 +125,24 @@ const ViewPackageWorkspace: React.FC<ViewPackageWorkspaceProps> = ({ exportPacka
|
|
|
119
125
|
t('triggerNewBuild', 'Trigger new build')
|
|
120
126
|
)}
|
|
121
127
|
</Button>
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
<div className={styles.secondaryActions}>
|
|
129
|
+
<Button
|
|
130
|
+
className={styles.secondaryButton}
|
|
131
|
+
kind="secondary"
|
|
132
|
+
onClick={editPackage}
|
|
133
|
+
disabled={isTriggeringBuild}
|
|
134
|
+
>
|
|
135
|
+
{t('edit', 'Edit')}
|
|
136
|
+
</Button>
|
|
137
|
+
<Button
|
|
138
|
+
className={styles.secondaryButton}
|
|
139
|
+
kind="danger--tertiary"
|
|
140
|
+
onClick={launchDeleteModal}
|
|
141
|
+
disabled={isTriggeringBuild}
|
|
142
|
+
>
|
|
143
|
+
{t('delete', 'Delete')}
|
|
144
|
+
</Button>
|
|
145
|
+
</div>
|
|
125
146
|
</section>
|
|
126
147
|
)}
|
|
127
148
|
|
|
@@ -188,4 +209,4 @@ const ViewPackageWorkspace: React.FC<ViewPackageWorkspaceProps> = ({ exportPacka
|
|
|
188
209
|
);
|
|
189
210
|
};
|
|
190
211
|
|
|
191
|
-
export default
|
|
212
|
+
export default ViewMetadataPackageWorkspace;
|
|
@@ -4,14 +4,14 @@ import userEvent from '@testing-library/user-event';
|
|
|
4
4
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
|
5
5
|
import { type Session, useSession, userHasAccess } from '@openmrs/esm-framework';
|
|
6
6
|
import ViewPackageActionButton from './view-package-action-button.component';
|
|
7
|
-
import {
|
|
7
|
+
import { launchViewMetadataPackageWorkspace } from '../view-metadata-package-utils';
|
|
8
8
|
import { type ExportPackage } from '../../../types';
|
|
9
9
|
|
|
10
|
-
vi.mock('../view-package-utils', () => ({
|
|
11
|
-
|
|
10
|
+
vi.mock('../view-metadata-package-utils', () => ({
|
|
11
|
+
launchViewMetadataPackageWorkspace: vi.fn(),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
-
const mockLaunchViewPackageWorkspace =
|
|
14
|
+
const mockLaunchViewPackageWorkspace = launchViewMetadataPackageWorkspace as Mock;
|
|
15
15
|
const mockUseSession = vi.mocked(useSession);
|
|
16
16
|
const mockUserHasAccess = vi.mocked(userHasAccess);
|
|
17
17
|
|
|
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { Button } from '@carbon/react';
|
|
4
4
|
import { ArrowRight } from '@carbon/react/icons';
|
|
5
5
|
import { useSession, userHasAccess } from '@openmrs/esm-framework';
|
|
6
|
-
import {
|
|
6
|
+
import { launchViewMetadataPackageWorkspace } from '../view-metadata-package-utils';
|
|
7
7
|
import { type ExportPackage } from '../../../types';
|
|
8
8
|
|
|
9
9
|
interface ViewPackageActionButtonProps {
|
|
@@ -17,7 +17,7 @@ const ViewPackageActionButton: React.FC<ViewPackageActionButtonProps> = ({ expor
|
|
|
17
17
|
const canView = session.user ? userHasAccess('Get Metadata Export Packages', session.user) : false;
|
|
18
18
|
|
|
19
19
|
const handleView = useCallback(() => {
|
|
20
|
-
|
|
20
|
+
launchViewMetadataPackageWorkspace(exportPackage);
|
|
21
21
|
}, [exportPackage]);
|
|
22
22
|
|
|
23
23
|
return (
|
package/src/index.ts
CHANGED
|
@@ -20,18 +20,18 @@ export const root = getAsyncLifecycle(() => import('./root.component'), options)
|
|
|
20
20
|
export const metadataExportCardLink = getAsyncLifecycle(() => import('./admin-card-link.component'), options);
|
|
21
21
|
|
|
22
22
|
// Modals
|
|
23
|
-
export const
|
|
24
|
-
() => import('./components/view-package/delete-package.modal'),
|
|
23
|
+
export const deleteMetadataPackageModal = getAsyncLifecycle(
|
|
24
|
+
() => import('./components/view-metadata-package/delete-metadata-package.modal'),
|
|
25
25
|
options,
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
// Workspaces
|
|
29
|
-
export const
|
|
30
|
-
() => import('./components/
|
|
29
|
+
export const metadataPackageFormWorkspace = getAsyncLifecycle(
|
|
30
|
+
() => import('./components/metadata-package-form/metadata-package-form.workspace'),
|
|
31
31
|
options,
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
export const
|
|
35
|
-
() => import('./components/view-package/view-package.workspace'),
|
|
34
|
+
export const viewMetadataPackageWorkspace = getAsyncLifecycle(
|
|
35
|
+
() => import('./components/view-metadata-package/view-metadata-package.workspace'),
|
|
36
36
|
options,
|
|
37
37
|
);
|