Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
10d7ce8
feat: add formdata to custom method in ocotillo data provider
chasetmartin Nov 20, 2025
7bb6615
feat: add well-inventory-bulk-import to AMPEditor list
chasetmartin Nov 20, 2025
7d39f77
feat: add well-inventory-bulk-import to apps resource
chasetmartin Nov 20, 2025
a191eae
feat: add well-inventory-bulk-import route
chasetmartin Nov 20, 2025
fb8cf13
feat: MVP well inventory bulk import page
chasetmartin Nov 20, 2025
c5c75bb
fix: change button wording
chasetmartin Nov 20, 2025
d314721
feat: attempt at validation error handling
chasetmartin Nov 21, 2025
be211f5
feat: first attempt well inventory table entry
chasetmartin Nov 21, 2025
b1375f7
feat: move actions and csv message
chasetmartin Nov 21, 2025
356a811
feat: install papaparse and npm audit fix
chasetmartin Nov 21, 2025
33fd302
feat: move csv parse to utils folder
chasetmartin Nov 21, 2025
48ac8e6
feat: use papaparse and move data grid defs
chasetmartin Nov 22, 2025
45d18fd
feat: remove add row button to keep as validation engine for csv import
chasetmartin Nov 24, 2025
1cb5945
feat: change instructions and no row display message
chasetmartin Nov 24, 2025
e785277
feat: add csv template button
chasetmartin Nov 24, 2025
5b9069c
feat: add a no validation error found success chip
chasetmartin Nov 24, 2025
95596a5
fix: change the no validation error message in chip
chasetmartin Nov 24, 2025
e8ef722
fix: move well inventory bulk import to top of apps
chasetmartin Nov 24, 2025
89189cd
fix: change import to upload to avoid confusion in validation workflow
chasetmartin Nov 24, 2025
b277c32
feat: update schemas to better match pydantic api schemas
chasetmartin Nov 25, 2025
d322c7d
feat: move submit button above table
chasetmartin Nov 25, 2025
9e064ba
refactor: move error and validation row mapping into utils
chasetmartin Nov 26, 2025
7a27a0e
fix: removes field name parsing helper to keep the field name consist…
chasetmartin Dec 2, 2025
e7c3529
fix: adds return statement and notification if user is able to select…
chasetmartin Dec 2, 2025
ecc11cb
fix: removes leftover TODO note
chasetmartin Dec 2, 2025
1e7f0c0
fix: move zod helpers into utils/zod folder
chasetmartin Dec 2, 2025
c2e40d1
fix: add returned type to handle csv import
chasetmartin Dec 2, 2025
0c4c375
Merge branch 'staging' into cm-well-inventory-table-validation
chasetmartin Dec 2, 2025
3dedcef
feat: add simple cypress test for happy path well inventory csv upload
chasetmartin Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions cypress/e2e/ocotillo/well-inventory-bulk.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// <reference types="cypress" />

describe('Well Inventory Bulk Import Page', () => {
beforeEach(() => {
cy.login()
cy.visit('/ocotillo/well-inventory-bulk-import')
})

it('should render the well inventory bulk import page UI without errors', () => {
cy.get('input').should('have.length.at.least', 1)
cy.contains('Well Inventory Bulk Import').should('exist')
cy.get('label').contains(/upload csv/i).should('exist')
cy.get('button').contains(/submit/i).should('exist')
})

it('should preserve CSV data through parse and unparse cycle', () => {
cy.readFile('cypress/fixtures/well-inventory.csv').then((originalCsv) => {
cy.intercept('POST', '**/well-inventory-csv', (req) => {
// For data sent as multipart string, check body contains CSV content
const body = req.body as string
const originalLines = originalCsv.split('\n')

Comment on lines +18 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cypress intercept misreads multipart payload

The upload flow posts a FormData (see handleSubmit creating a File and appending it to FormData), but the new Cypress test assumes req.body is a CSV string and immediately casts it to string. For multipart requests req.body is a FormData/Blob, so the include assertions will run against "[object FormData]" and always fail even when the CSV is sent correctly. This makes the added test a permanent false negative. Consider extracting the blob from req.body (e.g., req.body.get('file') and reading it) before asserting on its contents.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally tried this approach, but the Cypress interceptor does not allow the .get('file'), the submission is just sent as a large string in the body in the Cypress environment, from what I can tell.

// Check that key data from original CSV is in the submitted body
originalLines.forEach(line => {
if (line.trim()) {
//check all original csv fields for exact match in body
const fields = line.split(',')
fields.forEach(field => {
console.log("checking field: ", field.trim())
expect(body).to.include(field.trim())
})
}
})

req.reply({
statusCode: 200,
body: {
summary: { total_rows_processed: 1, total_rows_imported: 1, validation_errors_or_warnings: 0 },
wells: [],
validation_errors: []
}
})
}).as('submitCSV')

cy.get('input[type="file"]').selectFile('cypress/fixtures/well-inventory.csv', { force: true })
cy.wait(1500)
cy.get('button').contains(/submit/i).click()
cy.wait('@submitCSV')
})
})
})
2 changes: 2 additions & 0 deletions cypress/fixtures/well-inventory.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
project,well_name_point_id,site_name,date_time,field_staff,utm_easting,utm_northing,utm_zone,elevation_ft,elevation_method,measuring_point_height_ft,measuring_point_description
Test Project_2,WELL-002,SITE-001,2024-01-15 10:30:00,John Smith,352342,4040485,13N,5500.5,Survey-grade GPS,2.5,Top of casing
41 changes: 26 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@tiptap/react": "^2.9.1",
"@tiptap/starter-kit": "^2.9.1",
"@turf/turf": "^7.2.0",
"@types/papaparse": "^5.5.0",
"axios-auth-refresh": "^3.3.6",
"d3-polygon": "^3.0.1",
"echarts": "^5.6.0",
Expand All @@ -68,6 +69,7 @@
"mapbox-gl": "^3.0.0",
"mapbox-gl-style-switcher": "^1.0.11",
"pako": "^2.1.0",
"papaparse": "^5.5.3",
"proj4": "^2.15.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
Expand Down
57 changes: 57 additions & 0 deletions src/pages/ocotillo/well-inventory-bulk-import/grid-defs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { GridColDef } from '@mui/x-data-grid'
import { IconButton } from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete'
import { allFieldNames, requiredFields, numericFields, booleanFields } from './utils'
import type { TableRow } from './index'

export function createGridColumns(
getCellError: (rowId: number, fieldName: string) => boolean,
handleDeleteRow: (id: number) => void
): GridColDef<TableRow>[] {
// Create columns for all fields from schema
const dataColumns: GridColDef<TableRow>[] = allFieldNames.map((fieldName) => {
const isRequired = requiredFields.includes(fieldName)
const isNumeric = numericFields.includes(fieldName)
const isBoolean = booleanFields.includes(fieldName)

const baseColumn: GridColDef<TableRow> = {
field: fieldName,
headerName: fieldName,
width: 150,
editable: true,
cellClassName: (params) => {
return getCellError(params.row.id, fieldName) ? 'error-cell' : ''
},
}

if (isNumeric) {
baseColumn.type = 'number'
baseColumn.width = 130
} else if (isBoolean) {
baseColumn.type = 'boolean'
baseColumn.width = 120
}

return baseColumn
})

// Add actions column at the beginning
const actionsColumn: GridColDef<TableRow> = {
field: 'actions',
headerName: 'Actions',
width: 100,
sortable: false,
renderCell: (params) => (
<IconButton
size="small"
onClick={() => handleDeleteRow(params.row.id)}
color="error"
>
<DeleteIcon />
</IconButton>
),
}

return [actionsColumn, ...dataColumns]
}

Loading