mirror of
https://github.com/inventree/InvenTree.git
synced 2025-09-14 06:31:27 +00:00
[Feature] Data export plugins (#9096)
* Move data export code out of "importer" directory * Refactoring to allow data export via plugin * Add brief docs framework * Add basic DataExportMixin class * Pass context data through to the serializer * Extract custom serializer * Refactoring * Add builtin plugin for BomExport * More refactoring * Cleanup for UseForm hooks * Allow GET methods in forms * Create new 'exporter' app * Refactor imports * Run cleanup task on boot * Add enumeration for plugin mixin types * Refactor with_mixin call * Generate export options serializer * Pass plugin information through * Offload export functionality to the plugin * Generate output * Download generated file * Refactor frontend code * Generate params for downloading * Pass custom fields through to the plugin * Implement multi-level export for BOM data * Export supplier and manufacturer information * Export substitute data * Remove old BOM exporter * Export part parameter data * Try different app order * Use GET instead of POST request - Less 'dangerous' - no chance of performing a destructive operation * Fix for constructing query parameters - Ignore any undefined values! * Trying something * Revert to POST - Required, other query data are ignored * Fix spelling mistakes * Remove SettingsMixin * Revert python version * Fix for settings.py * Fix missing return * Fix for label mixin code * Run playwright tests in --host mode * Fix for choice field - Prevent empty value if field is required * Remove debug prints * Update table header * Playwright tests for data export * Rename app from "exporter" to "data_exporter" * Add frontend table for export sessions * Updated playwright testing * Fix for unit test * Fix build order unit test * Back to using GET instead of POST - Otherwise, users need POST permissions to export! - A bit of trickery with the forms architecture * Fix remaining unit tests * Implement unit test for BOM export - Including test for custom plugin * Fix unit test * Bump API version * Enhanced playwright tests * Add debug for CI testing * Single unit test only (for debugging) * Fix typo * typo fix * Remove debugs * Docs updates * Revert typo * Update tests * Serializer fix * Fix typo * Offload data export to the background worker - Requires mocking the original request object - Will need some further unit testing! * Refactor existing models into DataOutput - Remove LabelOutput table - Remove ReportOutput table - Remove ExportOutput table - Consolidate into single API endpoint * Remove "output" tables from frontend * Refactor frontend hook to be generic * Frontend now works with background data export * Fix tasks.py * Adjust unit tests * Revert 'plugin_key' to 'plugin' * Improve user checking when printing * Updates * Remove erroneous migration file * Tweak plugin registry * Adjust playwright tests * Refactor data export - Convert into custom hook - Enable for calendar view also * Add playwright tests * Adjust unit testing * Tweak unit tests * Add extra timeout to data export * Fix for RUF045
This commit is contained in:
@@ -97,6 +97,11 @@ test('Build Order - Calendar', async ({ page }) => {
|
||||
await navigate(page, 'manufacturing/index/buildorders');
|
||||
await activateCalendarView(page);
|
||||
|
||||
// Export calendar data
|
||||
await page.getByLabel('calendar-export-data').click();
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Check "part category" filter
|
||||
await page.getByLabel('calendar-select-filters').click();
|
||||
await page.getByRole('button', { name: 'Add Filter' }).click();
|
||||
@@ -104,6 +109,9 @@ test('Build Order - Calendar', async ({ page }) => {
|
||||
await page.getByRole('option', { name: 'Category', exact: true }).click();
|
||||
await page.getByLabel('related-field-filter-category').click();
|
||||
await page.getByText('Part category, level 1').waitFor();
|
||||
|
||||
// Required because we downloaded a file
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
test('Build Order - Edit', async ({ page }) => {
|
||||
|
120
src/frontend/tests/pui_exporting.spec.ts
Normal file
120
src/frontend/tests/pui_exporting.spec.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import test from '@playwright/test';
|
||||
import { globalSearch, loadTab, navigate } from './helpers';
|
||||
import { doQuickLogin } from './login';
|
||||
|
||||
// Helper function to open the export data dialog
|
||||
const openExportDialog = async (page) => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('table-export-data').click();
|
||||
await page.getByText('Export Format *', { exact: true }).waitFor();
|
||||
await page.getByText('Export Plugin *', { exact: true }).waitFor();
|
||||
};
|
||||
|
||||
// Test data export for various order types
|
||||
test('Exporting - Orders', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
|
||||
// Download list of purchase orders
|
||||
await navigate(page, 'purchasing/index/purchase-orders');
|
||||
|
||||
await openExportDialog(page);
|
||||
|
||||
// // Select export format
|
||||
await page.getByLabel('choice-field-export_format').click();
|
||||
await page.getByRole('option', { name: 'Excel' }).click();
|
||||
|
||||
// // Select export plugin (should only be one option here)
|
||||
await page.getByLabel('choice-field-export_plugin').click();
|
||||
await page.getByRole('option', { name: 'InvenTree Exporter' }).click();
|
||||
|
||||
// // Export the data
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Download list of purchase order items
|
||||
await page.getByRole('cell', { name: 'PO0011' }).click();
|
||||
await loadTab(page, 'Line Items');
|
||||
await openExportDialog(page);
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Download a list of build orders
|
||||
await navigate(page, 'manufacturing/index/buildorders/');
|
||||
await openExportDialog(page);
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Finally, navigate to the admin center and ensure the export data is available
|
||||
await navigate(page, 'settings/admin/export/');
|
||||
|
||||
// Check for expected outputs
|
||||
await page
|
||||
.getByRole('link', { name: /InvenTree_Build_.*\.csv/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole('link', { name: /InvenTree_PurchaseOrder_.*\.xlsx/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole('link', { name: /InvenTree_PurchaseOrderLineItem_.*\.csv/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
|
||||
// Delete all exported file outputs
|
||||
await page.getByRole('cell', { name: 'Select all records' }).click();
|
||||
await page.getByLabel('action-button-delete-selected').click();
|
||||
await page.getByRole('button', { name: 'Delete', exact: true }).click();
|
||||
await page.getByText('Items Deleted').waitFor();
|
||||
});
|
||||
|
||||
// Test for custom BOM exporter
|
||||
test('Exporting - BOM', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
|
||||
await globalSearch(page, 'MAST');
|
||||
await page.getByLabel('search-group-results-part').locator('a').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await loadTab(page, 'Bill of Materials');
|
||||
await openExportDialog(page);
|
||||
|
||||
// Select export format
|
||||
await page.getByLabel('choice-field-export_format').click();
|
||||
await page.getByRole('option', { name: 'TSV' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Select BOM plugin
|
||||
await page.getByLabel('choice-field-export_plugin').click();
|
||||
await page.getByRole('option', { name: 'BOM Exporter' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Now, adjust the settings specific to the BOM exporter
|
||||
await page.getByLabel('number-field-export_levels').fill('3');
|
||||
await page
|
||||
.locator('label')
|
||||
.filter({ hasText: 'Pricing DataInclude part' })
|
||||
.locator('span')
|
||||
.nth(1)
|
||||
.click();
|
||||
await page
|
||||
.locator('label')
|
||||
.filter({ hasText: 'Parameter DataInclude part' })
|
||||
.locator('span')
|
||||
.nth(1)
|
||||
.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Finally, navigate to the admin center and ensure the export data is available
|
||||
await navigate(page, 'settings/admin/export/');
|
||||
|
||||
await page.getByRole('cell', { name: 'bom-exporter' }).first().waitFor();
|
||||
await page
|
||||
.getByRole('link', { name: /InvenTree_BomItem_.*\.tsv/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
|
||||
// Required because we downloaded a file
|
||||
await page.context().close();
|
||||
});
|
@@ -41,7 +41,7 @@ test('Label Printing', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Print', exact: true }).isEnabled();
|
||||
await page.getByRole('button', { name: 'Print', exact: true }).click();
|
||||
|
||||
await page.getByText('Printing completed successfully').first().waitFor();
|
||||
await page.getByText('Process completed successfully').first().waitFor();
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ test('Report Printing', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Print', exact: true }).isEnabled();
|
||||
await page.getByRole('button', { name: 'Print', exact: true }).click();
|
||||
|
||||
await page.getByText('Printing completed successfully').first().waitFor();
|
||||
await page.getByText('Process completed successfully').first().waitFor();
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user