diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebdf11302..7c2c05d12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: run: dotnet build BDMS.sln -c Release /warnaserror - name: Start db and api's - run: docker compose up --wait minio db api-legacy api + run: docker compose up --wait minio db api-legacy api dataextraction - name: Run dotnet tests run: dotnet test BDMS.sln -c Release --no-build --verbosity normal --filter TestCategory!=LongRunning @@ -57,7 +57,7 @@ jobs: run: dotnet build BDMS.sln -c Release /warnaserror - name: Start db and api's - run: docker compose up --wait minio db api-legacy api oidc-server + run: docker compose up --wait minio db api-legacy api oidc-server dataextraction - working-directory: ./src/client run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 8680bb510..a4d20a892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - WMTS Services are now supported as custom user layers. - Added data extraction API. +- Added support to extract coordinates from a borehole attachment. ### Changed diff --git a/src/client/cypress/e2e/detailPage/labeling.cy.js b/src/client/cypress/e2e/detailPage/labeling.cy.js index 3241246c2..ead38e688 100644 --- a/src/client/cypress/e2e/detailPage/labeling.cy.js +++ b/src/client/cypress/e2e/detailPage/labeling.cy.js @@ -1,6 +1,5 @@ import { evaluateCoordinate, evaluateSelect, hasAiStyle, hasError, isDisabled } from "../helpers/formHelpers.js"; import { - interceptShowLabelingCall, newEditableBorehole, newUneditableBorehole, startBoreholeEditing, @@ -55,10 +54,6 @@ const waitForLabelingImageLoaded = () => { }; describe("Test labeling tool", () => { - beforeEach(() => { - interceptShowLabelingCall(); - }); - it("can show labeling panel", () => { newUneditableBorehole().as("borehole_id"); // only show in editing mode @@ -132,19 +127,15 @@ describe("Test labeling tool", () => { cy.get('[data-cy="button-select-popover"] .MuiListItem-root').eq(1).click(); cy.get('[data-cy="labeling-file-button-select"]').contains("borehole_attachment_3.pdf"); - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // Add this once the api returns the correct file - // // Cannot draw if the panel was opened with the panel toggle button - // waitForLabelingImageLoaded(); - // cy.window().then(win => { - // const interactions = win.labelingImage.getInteractions().getArray(); - // expect(interactions.some(interaction => interaction.constructor.name === "Draw")).to.be.false; - // }); + // Cannot draw if the panel was opened with the panel toggle button + waitForLabelingImageLoaded(); + cy.window().then(win => { + const interactions = win.labelingImage.getInteractions().getArray(); + expect(interactions.some(interaction => interaction.constructor.name === "Draw")).to.be.false; + }); }); - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // We have to wait for the docker integration before this test can be enabled - it.skip("can extract data from image", () => { + it("can extract data from image", () => { newEditableBorehole().as("borehole_id"); cy.get('[data-cy="labeling-toggle-button"]').click(); cy.get('[data-cy="labeling-file-dropzone"]').should("exist"); @@ -182,25 +173,43 @@ describe("Test labeling tool", () => { hasError("location_y_lv03", false); isDisabled("location_y_lv03"); - // Can draw box around coordinates and extract correct coordinates drawBox(400, 60, 600, 170); - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // Update all coordinates once api returns coordinates as floats evaluateSelect("spatial_reference_system", "20104001"); - evaluateCoordinate("location_x", "2'646'359"); + evaluateCoordinate("location_x", "2'646'359.7"); hasError("location_x", false); isDisabled("location_x", false); - evaluateCoordinate("location_y", "1'249'017"); + evaluateCoordinate("location_y", "1'249'017.82"); hasError("location_y", false); isDisabled("location_y", false); - evaluateCoordinate("location_x_lv03", "646'358"); + evaluateCoordinate("location_x_lv03", "646'358.97"); hasError("location_x_lv03", false); isDisabled("location_x_lv03", true); - evaluateCoordinate("location_y_lv03", "249'017"); + evaluateCoordinate("location_y_lv03", "249'017.66"); hasError("location_y_lv03", false); isDisabled("location_y_lv03", true); + }); + + // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 + // api call extract_data in drawBox times out + it.skip("can extract data from rotated and zoomed next page", () => { + newEditableBorehole().as("borehole_id"); + cy.get('[data-cy="labeling-toggle-button"]').click(); + cy.get('[data-cy="labeling-file-dropzone"]').should("exist"); + cy.get('[data-cy="labeling-file-selector"]').contains("No documents have been uploaded yet."); + + cy.get('[data-cy="labeling-file-dropzone"]').selectFile("cypress/fixtures/labeling_attachment.pdf", { + force: true, + mimeType: "application/pdf", + fileName: "labeling_attachment.pdf", + }); + + cy.wait("@get-borehole-files"); + waitForLabelingImageLoaded(); + cy.get('[data-cy="labeling-file-button-select"]').contains("labeling_attachment.pdf"); + cy.get('[data-cy="labeling-page-count"]').contains("1 / 3"); + cy.get('[data-cy="labeling-page-previous"]').should("be.disabled"); + cy.get('[data-cy="labeling-page-next"]').should("not.be.disabled"); - // Can draw after navigating to the next page and extract correct bbox from rotated, zoomed image cy.get('[data-cy="coordinate-segment"] [data-cy="labeling-button"]').click(); cy.get('[data-cy="labeling-page-next"]').click(); waitForLabelingImageLoaded(); @@ -240,8 +249,29 @@ describe("Test labeling tool", () => { evaluateCoordinate("location_y_lv03", "249'931"); hasError("location_y_lv03", false); isDisabled("location_y_lv03", false); + }); + + // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 + // api call extract_data in drawBox times out + it.skip("shows alert if no coordinates are extracted", () => { + newEditableBorehole().as("borehole_id"); + cy.get('[data-cy="labeling-toggle-button"]').click(); + cy.get('[data-cy="labeling-file-dropzone"]').should("exist"); + cy.get('[data-cy="labeling-file-selector"]').contains("No documents have been uploaded yet."); + + cy.get('[data-cy="labeling-file-dropzone"]').selectFile("cypress/fixtures/labeling_attachment.pdf", { + force: true, + mimeType: "application/pdf", + fileName: "labeling_attachment.pdf", + }); + + cy.wait("@get-borehole-files"); + waitForLabelingImageLoaded(); + cy.get('[data-cy="labeling-file-button-select"]').contains("labeling_attachment.pdf"); + cy.get('[data-cy="labeling-page-count"]').contains("1 / 3"); + cy.get('[data-cy="labeling-page-previous"]').should("be.disabled"); + cy.get('[data-cy="labeling-page-next"]').should("not.be.disabled"); - // Shows alert if no coordinates are extracted cy.get('[data-cy="labeling-page-next"]').click(); waitForLabelingImageLoaded(); cy.get('[data-cy="labeling-page-count"]').contains("3 / 3"); @@ -269,9 +299,9 @@ describe("Test labeling tool", () => { waitForLabelingImageLoaded(); drawBox(400, 60, 600, 170); evaluateSelect("spatial_reference_system", "20104001"); - evaluateCoordinate("location_x", "2'646'359"); - evaluateCoordinate("location_y", "1'249'017"); - evaluateCoordinate("location_x_lv03", "646'358"); - evaluateCoordinate("location_y_lv03", "249'017"); + evaluateCoordinate("location_x", "2'646'359.7"); + evaluateCoordinate("location_y", "1'249'017.82"); + evaluateCoordinate("location_x_lv03", "646'358.97"); + evaluateCoordinate("location_y_lv03", "249'017.66"); }); }); diff --git a/src/client/cypress/e2e/helpers/testHelpers.js b/src/client/cypress/e2e/helpers/testHelpers.js index a8afa73c7..aa98258ea 100644 --- a/src/client/cypress/e2e/helpers/testHelpers.js +++ b/src/client/cypress/e2e/helpers/testHelpers.js @@ -5,11 +5,6 @@ import { startEditing, stopEditing } from "./buttonHelpers.js"; export const bearerAuth = token => ({ bearer: token }); -export const interceptShowLabelingCall = () => { - cy.intercept("GET", "api/show-labeling-in-cypress-test", { - statusCode: 200, - }).as("show-labeling"); -}; export const interceptApiCalls = () => { // Api V1 cy.intercept("/api/v1/borehole").as("borehole"); @@ -107,9 +102,7 @@ export const interceptApiCalls = () => { method: "GET", url: "/api/v2/boreholefile/dataextraction/*", }).as("load-extraction-file"); - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // Check if path is correct - cy.intercept("http://localhost:8000/api/V1/extract_data").as("extract-data"); + cy.intercept("dataextraction/api/V1/extract_data").as("extract-data"); }; /** diff --git a/src/client/src/api/dataextraction.js b/src/client/src/api/dataextraction.js new file mode 100644 index 000000000..05a9c46ec --- /dev/null +++ b/src/client/src/api/dataextraction.js @@ -0,0 +1,26 @@ +import store from "../reducers"; +import { getAuthorizationHeader } from "./authentication"; + +export async function fetchCreatePngs(fileName) { + return await fetch("dataextraction/api/V1/create_pngs", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: getAuthorizationHeader(store.getState().core_user.authentication), + }, + body: JSON.stringify({ filename: fileName + ".pdf" }), + }); +} + +export async function fetchExtractData(request, abortSignal) { + return await fetch("dataextraction/api/V1/extract_data", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: getAuthorizationHeader(store.getState().core_user.authentication), + }, + body: JSON.stringify(request), + signal: abortSignal, + }); +} diff --git a/src/client/src/api/file/file.ts b/src/client/src/api/file/file.ts index 3e5dabe0d..195b27436 100644 --- a/src/client/src/api/file/file.ts +++ b/src/client/src/api/file/file.ts @@ -1,5 +1,6 @@ import { ExtractionRequest, ExtractionResponse } from "../../pages/detail/labeling/labelingInterfaces.tsx"; import { ApiError } from "../apiInterfaces.ts"; +import { fetchCreatePngs, fetchExtractData } from "../dataextraction"; import { download, fetchApiV2, fetchApiV2Base, upload } from "../fetchApiV2"; import { DataExtractionResponse, maxFileSizeKB } from "./fileInterfaces.ts"; @@ -82,28 +83,14 @@ export async function loadImage(fileName: string) { } export async function createExtractionPngs(fileName: string) { - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // Maybe update URL after proper integration - const response = await fetch("http://localhost:8000/api/V1/create_pngs", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ filename: fileName + ".pdf" }), - }); + const response = await fetchCreatePngs(fileName); if (!response.ok) { throw new ApiError("errorDataExtractionFileLoading", 500); } } export async function extractData(request: ExtractionRequest, abortSignal: AbortSignal): Promise { - // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 - // Maybe update URL after proper integration - const response = await fetch("http://localhost:8000/api/V1/extract_data", { - method: "POST", - headers: { "Content-Type": "application/json", Accept: "application/json" }, - body: JSON.stringify(request), - signal: abortSignal, - }); - + const response = await fetchExtractData(request, abortSignal); if (response.ok) { const responseObject = await response.json(); // TODO: https://github.com/swisstopo/swissgeol-boreholes-suite/issues/1546 diff --git a/src/client/src/pages/detail/detailPage.tsx b/src/client/src/pages/detail/detailPage.tsx index 93e9d6560..9244d9dd9 100644 --- a/src/client/src/pages/detail/detailPage.tsx +++ b/src/client/src/pages/detail/detailPage.tsx @@ -14,31 +14,16 @@ import LabelingPanel from "./labeling/labelingPanel.tsx"; interface DetailPageContentProps { editingEnabled: boolean; editableByCurrentUser: boolean; - showLabeling: boolean; } export const DetailPage: FC = () => { const [editingEnabled, setEditingEnabled] = useState(false); const [editableByCurrentUser, setEditableByCurrentUser] = useState(false); - const [showLabeling, setShowLabeling] = useState(false); const borehole: Borehole = useSelector((state: ReduxRootState) => state.core_borehole); const user = useSelector((state: ReduxRootState) => state.core_user); const location = useLocation(); const { panelPosition, panelOpen, togglePanel } = useLabelingContext(); - useEffect(() => { - // Fetch to be mocked in cypress test to show labeling area. - const checkLabelingVisibility = async () => { - try { - const response = await fetch("api/show-labeling-in-cypress-test"); - setShowLabeling(response.status === 200); - } catch { - /* fetch will fail outside of test environment so state should not be updated */ - } - }; - checkLabelingVisibility().catch(); - }, []); - useEffect(() => { setEditingEnabled(borehole.data.lock !== null); }, [borehole.data.lock]); @@ -68,7 +53,6 @@ export const DetailPage: FC = () => { const props: DetailPageContentProps = { editingEnabled: editingEnabled, editableByCurrentUser: editableByCurrentUser, - showLabeling: showLabeling, }; return ( @@ -93,7 +77,7 @@ export const DetailPage: FC = () => { width: panelOpen && panelPosition === "right" ? "50%" : "100%", height: panelOpen && panelPosition === "bottom" ? "50%" : "100%", }}> - {editingEnabled && showLabeling && ( + {editingEnabled && ( togglePanel()} /> )} diff --git a/src/client/src/pages/detail/detailPageContent.jsx b/src/client/src/pages/detail/detailPageContent.jsx index e4f00b4fe..aa4c43b4d 100644 --- a/src/client/src/pages/detail/detailPageContent.jsx +++ b/src/client/src/pages/detail/detailPageContent.jsx @@ -294,7 +294,6 @@ class DetailPageContent extends React.Component { updateChange={this.updateChange} editingEnabled={editingEnabled}> void; mapPointChange: boolean; setMapPointChange: React.Dispatch>; - showLabeling: boolean; } export interface Location { diff --git a/src/client/src/pages/detail/form/location/coordinatesSegment.tsx b/src/client/src/pages/detail/form/location/coordinatesSegment.tsx index 3bdc31d06..ee5e84062 100644 --- a/src/client/src/pages/detail/form/location/coordinatesSegment.tsx +++ b/src/client/src/pages/detail/form/location/coordinatesSegment.tsx @@ -31,7 +31,6 @@ const CoordinatesSegment: React.FC = ({ updateNumber, mapPointChange, setMapPointChange, - showLabeling, editingEnabled, }) => { const { t } = useTranslation(); @@ -400,7 +399,6 @@ const CoordinatesSegment: React.FC = ({ sx={{ p: 4, pb: 3 }} titleTypographyProps={{ variant: "h5" }} action={ - showLabeling && editingEnabled && ( void; } -const LocationSegment = ({ - borehole, - updateChange, - updateNumber, - showLabeling, - editingEnabled, -}: LocationSegmentProps) => { +const LocationSegment = ({ borehole, updateChange, updateNumber, editingEnabled }: LocationSegmentProps) => { const [mapPointChange, setMapPointChange] = useState(false); return ( @@ -35,7 +28,6 @@ const LocationSegment = ({ updateNumber={updateNumber} mapPointChange={mapPointChange} setMapPointChange={setMapPointChange} - showLabeling={showLabeling} editingEnabled={editingEnabled} /> path.replace(/^\/dataextraction/, ""), + }, }, port: 3000, },