From cb006d7eeaf0d379a1e0ca06c12cd68cdbfb7e4d Mon Sep 17 00:00:00 2001 From: Tyler Adam Martinez Date: Wed, 29 Apr 2026 09:44:18 -0500 Subject: [PATCH 1/2] chore(thing/well-show): Update PDF preview to only require view access --- src/pages/ocotillo/thing/well-show.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/ocotillo/thing/well-show.tsx b/src/pages/ocotillo/thing/well-show.tsx index 7db4699a..a9fcc791 100644 --- a/src/pages/ocotillo/thing/well-show.tsx +++ b/src/pages/ocotillo/thing/well-show.tsx @@ -84,9 +84,7 @@ export const WellShow = () => { // debugging. Visible only in the browser console when it is open. const label = `[ocotillo] GET thing/water-well/${id}/details` try { - const plain = JSON.parse( - JSON.stringify(data) - ) as IWellDetails + const plain = JSON.parse(JSON.stringify(data)) as IWellDetails console.log(label, plain) } catch { console.log(label, data) @@ -97,7 +95,7 @@ export const WellShow = () => { return data }, }) - const { canManageAmp } = useAccessCapabilities() + const { canViewAmp } = useAccessCapabilities() const { result: assetResult, query: assetQuery } = useList({ resource: 'asset', @@ -352,7 +350,7 @@ export const WellShow = () => { }} contentProps={{ sx: { pt: 1 } }} headerButtons={() => - canManageAmp ? ( + canViewAmp ? ( Date: Wed, 29 Apr 2026 10:38:47 -0500 Subject: [PATCH 2/2] test(FieldCompilationNotesPdf): Add pkg for decoding pdf text --- package-lock.json | 276 ++++++++++++++++++ package.json | 1 + .../FieldCompilationNotesPdf.test.ts | 43 +-- 3 files changed, 303 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9074bb7c..24707b24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "@vitest/ui": "^3.2.4", "cypress": "^15.0.0", "jsdom": "^26.1.0", + "pdfjs-dist": "^5.7.284", "prettier": "3.5.3", "tailwindcss": "^3.4.19", "typescript": "^5.4.2", @@ -2858,6 +2859,268 @@ } } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", + "dev": true, + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, "node_modules/@noble/ciphers": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", @@ -18407,6 +18670,19 @@ "pbf": "bin/pbf" } }, + "node_modules/pdfjs-dist": { + "version": "5.7.284", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.7.284.tgz", + "integrity": "sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.100" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/package.json b/package.json index d83d7846..68fe3cf3 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "@vitest/ui": "^3.2.4", "cypress": "^15.0.0", "jsdom": "^26.1.0", + "pdfjs-dist": "^5.7.284", "prettier": "3.5.3", "tailwindcss": "^3.4.19", "typescript": "^5.4.2", diff --git a/src/test/components/FieldCompilationNotesPdf.test.ts b/src/test/components/FieldCompilationNotesPdf.test.ts index 6cfa7f7a..bd1a8bf3 100644 --- a/src/test/components/FieldCompilationNotesPdf.test.ts +++ b/src/test/components/FieldCompilationNotesPdf.test.ts @@ -5,6 +5,7 @@ import { inflate } from 'pako' import { FieldCompilationNotesPdf } from '@/components/pdf/FieldCompilationNotesPdf' import type { IContact, IWell } from '@/interfaces/ocotillo' import { formatContactPhones } from '@/components/pdf/fieldCompilationPhoneFormatter' +import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs' const makeContact = (phones: NonNullable): IContact => ({ @@ -70,15 +71,27 @@ const decodePdfStreams = (pdfText: string) => { return decoded.join('\n') } -const decodePdfHexStrings = (decodedPdfText: string) => { - const fragments: string[] = [] +const extractPdfText = async (pdfBlob: Blob) => { + const data = new Uint8Array(await pdfBlob.arrayBuffer()) - for (const match of decodedPdfText.matchAll(/<([0-9A-Fa-f]+)>/g)) { - const hex = match[1] - fragments.push(Buffer.from(hex, 'hex').toString('latin1')) + const document = await pdfjsLib.getDocument({ + data, + }).promise + + const pages: string[] = [] + + for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber++) { + const page = await document.getPage(pageNumber) + const textContent = await page.getTextContent() + + pages.push(textContent.items.map((item: any) => item.str).join('')) } - return fragments.join('') + return { + pageCount: document.numPages, + text: pages.join('\n'), + pages, + } } describe('formatContactPhones', () => { @@ -155,7 +168,7 @@ describe('formatContactPhones', () => { }) describe('FieldCompilationNotesPdf', () => { - it('appends a final blank page with the requested text when no hydrograph image is provided', async () => { + it('renders all expected pages and content when no hydrograph image is provided', async () => { const pdfBlob = await pdf( createElement(FieldCompilationNotesPdf, { well: makeWell(), @@ -167,17 +180,13 @@ describe('FieldCompilationNotesPdf', () => { }) as any ).toBlob() - const pdfText = Buffer.from(await pdfBlob.arrayBuffer()).toString('latin1') + const { pageCount, pages } = await extractPdfText(pdfBlob) - const pageMatches = pdfText.match(/\/Type \/Page\b/g) ?? [] - const decodedText = decodePdfStreams(pdfText) - const decodedVisibleText = decodePdfHexStrings(decodedText) + expect(pageCount).toBe(4) - expect(pageMatches).toHaveLength(4) - expect(decodedVisibleText).toContain( - 'This page is intentionally left blank' - ) - expect(decodedVisibleText).toContain('Field Compilation Notes') - expect(decodedVisibleText).toContain('General Field Notes: Well-1') + expect(pages[0]).toContain('Field Compilation Notes') + expect(pages[1]).toContain('General Field Notes: Well-1') + expect(pages[2]).toContain('Hydrograph unavailable for this well') + expect(pages[3]).toContain('This page is intentionally left blank') }) })