diff --git a/src/__tests__/components/ExportTable.test.tsx b/src/__tests__/components/ExportTable.test.tsx index 4f89d379e..c2965d3c3 100644 --- a/src/__tests__/components/ExportTable.test.tsx +++ b/src/__tests__/components/ExportTable.test.tsx @@ -63,6 +63,7 @@ const renderTable = (props: Partial[0]> = {}) => compatibilitiesTables={null} exportTypeFile="csv" oneFile={false} + selectedTablesCount={1} {...props} /> @@ -99,4 +100,20 @@ describe('ExportTable', () => { renderTable({ exportTableSettings: { ...tableSetting, isChecked: false } as never }) expect(await screen.findByText('Patient')).toBeInTheDocument() }) + + it('signale la sous-table patient__identifier sur la table Patient', async () => { + renderTable({ exportTable: { ...tableInfo, name: 'Patient' } }) + expect(await screen.findByText(/patient__identifier sera également exportée/)).toBeInTheDocument() + }) + + it("masque la mention de patient__identifier en export regroupé lorsqu'une autre table est sélectionnée", async () => { + renderTable({ exportTable: { ...tableInfo, name: 'Patient' }, oneFile: true, selectedTablesCount: 2 }) + await screen.findAllByText('Patient') + expect(screen.queryByText(/patient__identifier sera également exportée/)).not.toBeInTheDocument() + }) + + it('conserve la mention de patient__identifier en export regroupé sur la seule table Patient', async () => { + renderTable({ exportTable: { ...tableInfo, name: 'Patient' }, oneFile: true, selectedTablesCount: 1 }) + expect(await screen.findByText(/patient__identifier sera également exportée/)).toBeInTheDocument() + }) }) diff --git a/src/__tests__/services/serviceExportCohort.test.ts b/src/__tests__/services/serviceExportCohort.test.ts index 0e0322b92..c1bf27df3 100644 --- a/src/__tests__/services/serviceExportCohort.test.ts +++ b/src/__tests__/services/serviceExportCohort.test.ts @@ -180,6 +180,86 @@ describe('serviceExportCohort.postExportCohort', () => { ) }) + it('ajoute automatiquement patient__identifier quand Patient est sélectionné', async () => { + mockPost.mockResolvedValue(asAxios({ uuid: 'exp-3' })) + await postExportCohort({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cohortId: { uuid: 'cohort-3' } as any, + motivation: 'analyse', + group_tables: false, + outputFormat: 'csv', + tables: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'Patient', respectTableRelationships: true, columns: null, fhirFilter: null } as any + ] + }) + const payload = mockPost.mock.calls[0][1] as { export_tables: { table_name: string; cohort_result_source: string }[] } + const tableNames = payload.export_tables.map((table) => table.table_name) + expect(tableNames).toEqual(['Patient', 'patient__identifier']) + payload.export_tables.forEach((table) => { + expect(table.cohort_result_source).toBe('cohort-3') + }) + }) + + it('ne duplique pas une sous-table liée déjà sélectionnée', async () => { + mockPost.mockResolvedValue(asAxios({ uuid: 'exp-4' })) + await postExportCohort({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cohortId: { uuid: 'cohort-4' } as any, + motivation: 'analyse', + group_tables: false, + outputFormat: 'csv', + tables: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'Patient', respectTableRelationships: true, columns: null, fhirFilter: null } as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'patient__identifier', respectTableRelationships: true, columns: null, fhirFilter: null } as any + ] + }) + const payload = mockPost.mock.calls[0][1] as { export_tables: { table_name: string }[] } + const tableNames = payload.export_tables.map((table) => table.table_name) + expect(tableNames).toEqual(['Patient', 'patient__identifier']) + expect(tableNames.filter((name) => name === 'patient__identifier')).toHaveLength(1) + }) + + it("n'ajoute pas patient__identifier en export regroupé quand une autre table est sélectionnée", async () => { + mockPost.mockResolvedValue(asAxios({ uuid: 'exp-5' })) + await postExportCohort({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cohortId: { uuid: 'cohort-5' } as any, + motivation: 'analyse', + group_tables: true, + outputFormat: 'csv', + tables: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'Patient', respectTableRelationships: true, columns: null, fhirFilter: null } as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'visit_occurrence', respectTableRelationships: true, columns: null, fhirFilter: null } as any + ] + }) + const payload = mockPost.mock.calls[0][1] as { export_tables: { table_name: string }[] } + const tableNames = payload.export_tables.map((table) => table.table_name) + expect(tableNames).toEqual(['Patient', 'visit_occurrence']) + }) + + it('ajoute patient__identifier en export regroupé quand Patient est la seule table sélectionnée', async () => { + mockPost.mockResolvedValue(asAxios({ uuid: 'exp-6' })) + await postExportCohort({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cohortId: { uuid: 'cohort-6' } as any, + motivation: 'analyse', + group_tables: true, + outputFormat: 'csv', + tables: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { tableName: 'Patient', respectTableRelationships: true, columns: null, fhirFilter: null } as any + ] + }) + const payload = mockPost.mock.calls[0][1] as { export_tables: { table_name: string }[] } + const tableNames = payload.export_tables.map((table) => table.table_name) + expect(tableNames).toEqual(['Patient', 'patient__identifier']) + }) + it('omet fhir_filter quand aucun filtre n’est fourni', async () => { mockPost.mockResolvedValue(asAxios({ uuid: 'exp-2' })) await postExportCohort({ diff --git a/src/pages/ExportRequest/components/ExportForm/index.tsx b/src/pages/ExportRequest/components/ExportForm/index.tsx index 30b8de686..b2ef7bde5 100644 --- a/src/pages/ExportRequest/components/ExportForm/index.tsx +++ b/src/pages/ExportRequest/components/ExportForm/index.tsx @@ -388,6 +388,7 @@ const ExportForm: React.FC = () => { compatibilitiesTables={compatibilitiesTables} exportTypeFile={exportTypeFile} oneFile={oneFile} + selectedTablesCount={tablesSettings.length} /> )) }, [ @@ -400,7 +401,8 @@ const ExportForm: React.FC = () => { onChangeTableSettings, compatibilitiesTables, exportTypeFile, - oneFile + oneFile, + tablesSettings.length ]) return ( diff --git a/src/pages/ExportRequest/components/ExportTable/index.tsx b/src/pages/ExportRequest/components/ExportTable/index.tsx index 9e55e81e1..0754da20d 100644 --- a/src/pages/ExportRequest/components/ExportTable/index.tsx +++ b/src/pages/ExportRequest/components/ExportTable/index.tsx @@ -60,6 +60,7 @@ type ExportTableProps = { compatibilitiesTables: string[] | null exportTypeFile: 'xlsx' | 'csv' oneFile: boolean + selectedTablesCount: number } /** @@ -102,7 +103,8 @@ const ExportTable: React.FC = ({ onChangeTableSettings, compatibilitiesTables, exportTypeFile, - oneFile + oneFile, + selectedTablesCount }) => { const dispatch = useAppDispatch() const userId = useAppSelector((state) => state.me?.id) @@ -294,6 +296,11 @@ const ExportTable: React.FC = ({ {']'} + {exportTable.name === 'Patient' && (!oneFile || selectedTablesCount === 1) && ( + + La sous-table patient__identifier sera également exportée avec la table Patient. + + )} diff --git a/src/services/aphp/serviceExportCohort.ts b/src/services/aphp/serviceExportCohort.ts index 234f45494..146e2f149 100644 --- a/src/services/aphp/serviceExportCohort.ts +++ b/src/services/aphp/serviceExportCohort.ts @@ -135,6 +135,10 @@ export const fetchExportsList = async ( } } +const AUTO_LINKED_TABLES: Record = { + Patient: ['patient__identifier'] +} + export const postExportCohort = async ({ cohortId, motivation, @@ -151,18 +155,41 @@ export const postExportCohort = async ({ const nominative = true const shift_date = false + const export_tables = tables.map((table: TableSetting) => ({ + table_name: table.tableName, + cohort_result_source: cohortId?.uuid, + respect_table_relationships: table.respectTableRelationships, + columns: table.columns, + ...(table.fhirFilter && { fhir_filter: table.fhirFilter?.uuid }), + pivot_merge_columns: table.pivotMergeColumns, + //pivot_split_columns : table.pivotSplitColumns, + pivot_merge_ids: table.pivotMergeIds + })) + // En export regroupé, une sous-table ne partage un lien hamiltonien avec sa table parente que si + // celle-ci est seule : au-delà, le dataexporter refuse la jointure sur clé primaire. + if (!group_tables || tables.length === 1) { + const existingTableNames = new Set(export_tables.map((table) => table.table_name)) + tables.forEach((table: TableSetting) => { + const linkedTables = AUTO_LINKED_TABLES[table.tableName] + if (!linkedTables) return + linkedTables.forEach((linkedTableName) => { + if (existingTableNames.has(linkedTableName)) return + existingTableNames.add(linkedTableName) + export_tables.push({ + table_name: linkedTableName, + cohort_result_source: cohortId?.uuid, + respect_table_relationships: table.respectTableRelationships, + columns: null, + pivot_merge_columns: undefined, + pivot_merge_ids: undefined + }) + }) + }) + } + return await apiBackend.post('/exports/', { motivation, - export_tables: tables.map((table: TableSetting) => ({ - table_name: table.tableName, - cohort_result_source: cohortId?.uuid, - respect_table_relationships: table.respectTableRelationships, - columns: table.columns, - ...(table.fhirFilter && { fhir_filter: table.fhirFilter?.uuid }), - pivot_merge_columns: table.pivotMergeColumns, - //pivot_split_columns : table.pivotSplitColumns, - pivot_merge_ids: table.pivotMergeIds - })), + export_tables, nominative: nominative, shift_date: shift_date, output_format: outputFormat,