diff --git a/app/controllers/concerns/extraction_redux_state.rb b/app/controllers/concerns/extraction_redux_state.rb index e85fc5d7..2e881b78 100644 --- a/app/controllers/concerns/extraction_redux_state.rb +++ b/app/controllers/concerns/extraction_redux_state.rb @@ -61,7 +61,7 @@ def extraction_app_details_slice def ui_extraction_app_details_slice { - activeRequest: @extraction_definition.requests.first.id, + activeRequest: active_request_id, sharedDefinitionsTabActive: false } end @@ -107,4 +107,10 @@ def ui_request_entity(request) loading: false } end + + def active_request_id + return @extraction_definition.requests.first.id if @extraction_definition.harvest? + + @extraction_definition.requests.last.id + end end diff --git a/app/controllers/concerns/transformation_redux_state.rb b/app/controllers/concerns/transformation_redux_state.rb index 7911e81c..d90d0956 100644 --- a/app/controllers/concerns/transformation_redux_state.rb +++ b/app/controllers/concerns/transformation_redux_state.rb @@ -62,7 +62,8 @@ def ui_field_entity(field) deleting: false, running: false, hasRun: false, - displayed: false + displayed: false, + active: false } end diff --git a/app/controllers/extraction_definitions_controller.rb b/app/controllers/extraction_definitions_controller.rb index 7d49edf2..358dc02a 100644 --- a/app/controllers/extraction_definitions_controller.rb +++ b/app/controllers/extraction_definitions_controller.rb @@ -5,18 +5,12 @@ class ExtractionDefinitionsController < ApplicationController before_action :find_pipeline before_action :find_harvest_definition - before_action :find_extraction_definition, only: %i[show update clone destroy edit] - before_action :find_destinations, only: %i[create update new edit] + before_action :find_extraction_definition, only: %i[show update clone destroy] + before_action :find_destinations, only: %i[create update] before_action :assign_show_variables, only: %i[show update] def show; end - def new - @extraction_definition = ExtractionDefinition.new(kind: params[:kind]) - end - - def edit; end - def create @extraction_definition = ExtractionDefinition.new(extraction_definition_params) @@ -25,11 +19,11 @@ def create 2.times { Request.create(extraction_definition: @extraction_definition) } - redirect_to create_redirect_path, notice: t('.success') + redirect_to pipeline_harvest_definition_extraction_definition_path( + @pipeline, @harvest_definition, @extraction_definition + ), notice: t('.success') else - flash.alert = t('.failure') - - redirect_to pipeline_path(@pipeline) + redirect_to pipeline_path(@pipeline), alert: t('.failure') end end @@ -46,21 +40,6 @@ def update end end - def test_record_extraction - @extraction_definition = ExtractionDefinition.new(extraction_definition_params) - - render json: Extraction::RecordExtraction.new(@extraction_definition, 1).extract - end - - def test_enrichment_extraction - @extraction_definition = ExtractionDefinition.new(extraction_definition_params) - - api_records = Extraction::RecordExtraction.new(@extraction_definition, 1).extract - records = JSON.parse(api_records.body)['records'] - - render json: Extraction::EnrichmentExtraction.new(@extraction_definition, records.first, 1).extract - end - def destroy if @extraction_definition.destroy redirect_to pipeline_path(@pipeline), notice: t('.success') @@ -89,18 +68,7 @@ def clone def assign_show_variables @parameters = @extraction_definition.parameters.order(created_at: :desc) @props = extraction_app_state - end - - def create_redirect_path - return pipeline_path(@pipeline) unless @extraction_definition.harvest? - - pipeline_harvest_definition_extraction_definition_path(@pipeline, @harvest_definition, @extraction_definition) - end - - def update_redirect_path - return pipeline_path(@pipeline) unless @extraction_definition.harvest? - - pipeline_harvest_definition_extraction_definition_path(@pipeline, @harvest_definition, @extraction_definition) + @destinations = Destination.all end def successful_clone_path(clone) diff --git a/app/controllers/extraction_jobs_controller.rb b/app/controllers/extraction_jobs_controller.rb index 45fadd96..bb2ce2e3 100644 --- a/app/controllers/extraction_jobs_controller.rb +++ b/app/controllers/extraction_jobs_controller.rb @@ -54,8 +54,6 @@ def json_create @extraction_job = ExtractionJob.create(extraction_definition: @extraction_definition, kind: params[:kind]) ExtractionWorker.perform_async(@extraction_job.id) - return render json: { location: pipeline_path(@pipeline) } unless params[:type] == 'transform' - render json: { location: pipeline_harvest_definition_transformation_definition_path(@pipeline, @harvest_definition, create_or_update_transformation_definition) @@ -68,7 +66,7 @@ def create_or_update_transformation_definition else transformation_definition = TransformationDefinition.create( extraction_job_id: @extraction_job.id, - pipeline_id: @pipeline.id + pipeline_id: @pipeline.id, kind: @extraction_definition.kind ) @harvest_definition.update(transformation_definition_id: transformation_definition.id) diff --git a/app/controllers/harvest_definitions_controller.rb b/app/controllers/harvest_definitions_controller.rb index d1b0e717..d5cce3da 100644 --- a/app/controllers/harvest_definitions_controller.rb +++ b/app/controllers/harvest_definitions_controller.rb @@ -18,9 +18,7 @@ def create else flash.alert = t('.failure', kind: harvest_kind) - @enrichment_definition = HarvestDefinition.new(pipeline: @pipeline) - - render 'pipelines/show' + redirect_to pipeline_path(@pipeline) end end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index e166875d..43572b04 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -3,18 +3,14 @@ class RequestsController < ApplicationController include LastEditedBy + before_action :find_extraction_definition, only: %w[show] + def show @request = Request.find(params[:id]) - if params[:previous_request_id].present? - @previous_request = Request.find(params[:previous_request_id]) - - @previous_response = Extraction::DocumentExtraction.new(@previous_request).extract - end + return harvest_request if @request.extraction_definition.harvest? - render json: @request.to_h.merge( - preview: Extraction::DocumentExtraction.new(@request, nil, @previous_response).extract - ) + enrichment_request end def update @@ -30,6 +26,60 @@ def update private + def find_extraction_definition + @extraction_definition = ExtractionDefinition.find(params[:extraction_definition_id]) + end + + def harvest_request + if params[:previous_request_id].present? + @previous_request = @extraction_definition.requests.find(params[:previous_request_id]) + + @previous_response = Extraction::DocumentExtraction.new(@previous_request).extract + end + + render json: @request.to_h.merge( + preview: Extraction::DocumentExtraction.new(@request, nil, @previous_response).extract + ) + end + + def enrichment_request + parsed_body = JSON.parse(api_response.body) + + if @request.first_request? + render json: first_enrichment_request_response(parsed_body) + else + render json: second_enrichment_request_response + end + end + + def page_param + params[:page] || 1 + end + + def record_param + params[:record] || 1 + end + + def api_response + Extraction::RecordExtraction.new(@request, page_param).extract + end + + def api_record + Extraction::ApiResponse.new(api_response).record(record_param.to_i - 1) + end + + def first_enrichment_request_response(parsed_body) + @request.to_h.merge(preview: { + **api_record.to_hash, + **parsed_body['meta'], + total_records: parsed_body['records'].count + }) + end + + def second_enrichment_request_response + @request.to_h.merge(preview: Extraction::EnrichmentExtraction.new(@request, api_record).extract) + end + def request_params params.require(:request).permit(:http_method) end diff --git a/app/frontend/entrypoints/application.js b/app/frontend/entrypoints/application.js index ac2258f3..d4ac542b 100644 --- a/app/frontend/entrypoints/application.js +++ b/app/frontend/entrypoints/application.js @@ -32,9 +32,6 @@ console.log( import * as bootstrap from "bootstrap"; import "/js/ClearField"; -import "/js/TestRecordExtraction"; -import "/js/TestEnrichmentExtraction"; -import "/js/TestTransformationRecordSelector"; import "/js/TestDestination"; import "/js/Tooltips"; import "/js/Toasts"; diff --git a/app/frontend/js/TestEnrichmentExtraction.js b/app/frontend/js/TestEnrichmentExtraction.js deleted file mode 100644 index e9e6de9f..00000000 --- a/app/frontend/js/TestEnrichmentExtraction.js +++ /dev/null @@ -1,32 +0,0 @@ -import { bindTestForm } from "./utils/test-form"; -import editor from "./editor"; -import xmlFormat from "xml-formatter"; - -bindTestForm( - "test_enrichment_extraction", - "js-test-enrichment-extraction-button", - "js-extraction-definition-form", - (response, _alertClass) => { - let results = response.data.body; - let format = document.querySelector("#extraction_definition_format").value; - - if (format == "JSON") { - results = JSON.stringify(response.data.body, null, 2); - } else if (format == "XML") { - results = xmlFormat(response.data.body, { - indentation: " ", - lineSeparator: "\n", - }); - } - - editor("#js-enrichment-extraction-result", format, true, results); - }, - (error) => { - document.querySelector("#js-enrichment-extraction-result").innerHTML = ""; - document.getElementById( - "js-enrichment-extraction-result" - ).innerHTML = ``; - } -); diff --git a/app/frontend/js/TestRecordExtraction.js b/app/frontend/js/TestRecordExtraction.js deleted file mode 100644 index b5cc2258..00000000 --- a/app/frontend/js/TestRecordExtraction.js +++ /dev/null @@ -1,30 +0,0 @@ -import { bindTestForm } from "./utils/test-form"; -import { EditorState } from "@codemirror/state"; -import { EditorView, basicSetup } from "codemirror"; -import { json } from "@codemirror/lang-json"; - -bindTestForm( - "test_record_extraction", - "js-test-record-extraction-button", - "js-extraction-definition-form", - (response, _alertClass) => { - let editor = new EditorView({ - state: EditorState.create({ - extensions: [basicSetup, json(), EditorState.readOnly.of(true)], - doc: JSON.stringify(JSON.parse(response.data.body), null, 2), - }), - parent: document.body, - }); - - document.querySelector("#js-record-extraction-result").innerHTML = ""; - document.querySelector("#js-record-extraction-result").append(editor.dom); - }, - () => { - document.querySelector("#js-record-extraction-result").innerHTML = ""; - document.getElementById( - "js-record-extraction-result" - ).innerHTML = ``; - } -); diff --git a/app/frontend/js/TestTransformationRecordSelector.js b/app/frontend/js/TestTransformationRecordSelector.js deleted file mode 100644 index f7e34d2b..00000000 --- a/app/frontend/js/TestTransformationRecordSelector.js +++ /dev/null @@ -1,23 +0,0 @@ -import { bindTestForm } from "./utils/test-form"; -import editor from "./editor"; -import xmlFormat from "xml-formatter"; - -bindTestForm( - "test", - "js-test-transformation-record-selector-button", - "js-transformation-definition-form", - (response, _alertClass) => { - let results = response.data.result; - - if (response.data.format == "JSON") { - results = JSON.stringify(response.data.result, null, 2); - } else if (response.data.format == "XML") { - results = xmlFormat(response.data.result, { - indentation: " ", - lineSeparator: "\n", - }); - } - - editor("#js-record-selector-result", response.data.format, true, results); - } -); diff --git a/app/frontend/js/apps/ExtractionApp/components/EnrichmentPreviewModal.jsx b/app/frontend/js/apps/ExtractionApp/components/EnrichmentPreviewModal.jsx new file mode 100644 index 00000000..d6e67b46 --- /dev/null +++ b/app/frontend/js/apps/ExtractionApp/components/EnrichmentPreviewModal.jsx @@ -0,0 +1,196 @@ +import React, { useState, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { useSelector, useDispatch } from "react-redux"; +import { selectAppDetails } from "~/js/features/ExtractionApp/AppDetailsSlice"; +import { request } from "~/js/utils/request"; + +import Modal from "react-bootstrap/Modal"; +import Preview from "~/js/apps/ExtractionApp/components/Preview"; + +import { previewRequest } from "~/js/features/ExtractionApp/RequestsSlice"; + +import { setLoading } from "~/js/features/ExtractionApp/UiRequestsSlice"; +import { selectUiRequestById } from "~/js/features/ExtractionApp/UiRequestsSlice"; +import { selectRequestById } from "~/js/features/ExtractionApp/RequestsSlice"; + +const EnrichmentPreviewModal = ({ + showModal, + handleClose, + initialRequestId, + mainRequestId, +}) => { + const dispatch = useDispatch(); + const appDetails = useSelector(selectAppDetails); + + const [currentPage, setCurrentPage] = useState(1); + const [currentRecord, setCurrentRecord] = useState(1); + + const initialRequestLoading = useSelector((state) => + selectUiRequestById(state, initialRequestId) + ).loading; + const mainRequestLoading = useSelector((state) => + selectUiRequestById(state, mainRequestId) + ).loading; + + const { total_pages, total_records } = useSelector((state) => + selectRequestById(state, initialRequestId) + ).preview; + + useEffect(() => { + requestNewPreview(); + }, [currentPage, currentRecord]); + + const handleNextRecordClick = async () => { + if (currentRecord < total_records) { + setCurrentRecord(currentRecord + 1); + } else { + setCurrentPage(currentPage + 1); + setCurrentRecord(1); + } + }; + + const handlePreviousRecordClick = () => { + if (currentRecord > 1) { + setCurrentRecord(currentRecord - 1); + } else { + setCurrentPage(currentPage - 1); + setCurrentRecord(total_records); + } + }; + + const handleSampleClick = async () => { + const response = await request + .post( + `/pipelines/${appDetails.pipeline.id}/harvest_definitions/${appDetails.harvestDefinition.id}/extraction_definitions/${appDetails.extractionDefinition.id}/extraction_jobs.json?kind=sample` + ) + .then((response) => { + return response; + }); + + window.location.replace(response.data.location); + }; + + const requestNewPreview = async () => { + dispatch(setLoading(initialRequestId)); + dispatch(setLoading(mainRequestId)); + + const initialPreview = await dispatch( + previewRequest({ + harvestDefinitionId: appDetails.harvestDefinition.id, + pipelineId: appDetails.pipeline.id, + extractionDefinitionId: appDetails.extractionDefinition.id, + id: initialRequestId, + page: currentPage, + record: currentRecord, + }) + ); + + dispatch( + previewRequest({ + harvestDefinitionId: appDetails.harvestDefinition.id, + pipelineId: appDetails.pipeline.id, + extractionDefinitionId: appDetails.extractionDefinition.id, + id: mainRequestId, + previousRequestId: initialPreview.payload.id, + page: currentPage, + record: currentRecord, + }) + ); + }; + + const canNotClickPreviousRecord = () => { + return ( + initialRequestLoading || + mainRequestLoading || + (currentPage == 1 && currentRecord == 1) + ); + }; + + const canNotClickNextRecord = () => { + return ( + initialRequestLoading || + mainRequestLoading || + (currentPage == total_pages && currentRecord == total_records) + ); + }; + + return createPortal( + + + Enrichment Extraction preview + +
+ + + +
+
+ +
+ Record {currentRecord} / {total_records} | Page {currentPage} /{" "} + {total_pages} +
+ +
+ + + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+
, + document.getElementById("react-modals") + ); +}; + +export default EnrichmentPreviewModal; diff --git a/app/frontend/js/apps/ExtractionApp/components/HeaderActions.jsx b/app/frontend/js/apps/ExtractionApp/components/HeaderActions.jsx index 265b07f5..e0264af4 100644 --- a/app/frontend/js/apps/ExtractionApp/components/HeaderActions.jsx +++ b/app/frontend/js/apps/ExtractionApp/components/HeaderActions.jsx @@ -8,6 +8,7 @@ import { previewRequest, } from "~/js/features/ExtractionApp/RequestsSlice"; import PreviewModal from "~/js/apps/ExtractionApp/components/PreviewModal"; +import EnrichmentPreviewModal from "~/js/apps/ExtractionApp/components/EnrichmentPreviewModal"; const HeaderActions = () => { const dispatch = useDispatch(); @@ -33,10 +34,15 @@ const HeaderActions = () => { pipelineId: appDetails.pipeline.id, extractionDefinitionId: appDetails.extractionDefinition.id, id: initialRequestId, + page: 1, + record: 1, }) ); - if (appDetails.extractionDefinition.paginated) { + if ( + appDetails.extractionDefinition.paginated || + appDetails.extractionDefinition.kind == "enrichment" + ) { dispatch( previewRequest({ harvestDefinitionId: appDetails.harvestDefinition.id, @@ -44,6 +50,8 @@ const HeaderActions = () => { extractionDefinitionId: appDetails.extractionDefinition.id, id: mainRequestId, previousRequestId: initialPreview.payload.id, + page: 1, + record: 1, }) ); } @@ -55,12 +63,23 @@ const HeaderActions = () => { Preview - + {appDetails.extractionDefinition.kind == "harvest" && ( + + )} + + {appDetails.extractionDefinition.kind == "enrichment" && ( + + )} , document.getElementById("react-header-actions") ); diff --git a/app/frontend/js/apps/ExtractionApp/components/Parameter.jsx b/app/frontend/js/apps/ExtractionApp/components/Parameter.jsx index 87938e6b..38af8d31 100644 --- a/app/frontend/js/apps/ExtractionApp/components/Parameter.jsx +++ b/app/frontend/js/apps/ExtractionApp/components/Parameter.jsx @@ -122,6 +122,7 @@ const Parameter = ({ id }) => { const valueColumnClasses = classNames({ "col-sm-11": kind == "slug", + "col-sm-6": kind != "slug", }); const displayName = () => { @@ -283,7 +284,7 @@ const Parameter = ({ id }) => { )} -
+
{content_type == "incremental" && ( diff --git a/app/frontend/js/apps/ExtractionApp/components/ParameterNavigationListItem.jsx b/app/frontend/js/apps/ExtractionApp/components/ParameterNavigationListItem.jsx index a61c2c91..9e9127bb 100644 --- a/app/frontend/js/apps/ExtractionApp/components/ParameterNavigationListItem.jsx +++ b/app/frontend/js/apps/ExtractionApp/components/ParameterNavigationListItem.jsx @@ -4,9 +4,10 @@ import { selectParameterById } from "~/js/features/ExtractionApp/ParametersSlice import { selectUiParameterById, toggleDisplayParameter, + setActiveParameter, } from "~/js/features/ExtractionApp/UiParametersSlice"; -const ParameterNavigationListItem = ({ id, index }) => { +const ParameterNavigationListItem = ({ id }) => { const dispatch = useDispatch(); const { name, kind, content } = useSelector((state) => @@ -24,14 +25,24 @@ const ParameterNavigationListItem = ({ id, index }) => { return name; }; + const handleListItemClick = () => { + const desiredDisplaySetting = !displayed; + + dispatch( + toggleDisplayParameter({ id: id, displayed: desiredDisplaySetting }) + ); + + if (desiredDisplaySetting == true) { + dispatch(setActiveParameter(id)); + } + }; + return (
  • - dispatch(toggleDisplayParameter({ id: id, displayed: !displayed })) - } + onClick={() => handleListItemClick()} href={`#parameter-${id}`} role="button" aria-expanded={displayed} diff --git a/app/frontend/js/apps/ExtractionApp/components/Preview.jsx b/app/frontend/js/apps/ExtractionApp/components/Preview.jsx index e41cbaf0..444c9cd6 100644 --- a/app/frontend/js/apps/ExtractionApp/components/Preview.jsx +++ b/app/frontend/js/apps/ExtractionApp/components/Preview.jsx @@ -8,7 +8,7 @@ import { selectRequestById } from "~/js/features/ExtractionApp/RequestsSlice"; import CodeEditor from "~/js/components/CodeEditor"; -const Preview = ({ id }) => { +const Preview = ({ id, view = "accordion" }) => { const { loading } = useSelector((state) => selectUiRequestById(state, id)); const { preview, format } = useSelector((state) => selectRequestById(state, id) @@ -24,121 +24,134 @@ const Preview = ({ id }) => { ); }; - return ( - <> - {loading && ( -
    -
    - Loading... -
    -
    - )} - {!loading && ( - <> -
    -
    -

    - -

    -
    -
    - {preview.method == "GET" && ( - - {preview.url} - - )} - {preview.method == "POST" && preview.url} - {preview.method == "POST" && formattedPayload()} -
    + const accordionView = () => { + return ( + <> +
    +
    +

    + +

    +
    +
    + {preview.method == "GET" && ( + + {preview.url} + + )} + {preview.method == "POST" && preview.url} + {preview.method == "POST" && formattedPayload()}
    +
    -
    -

    - -

    -
    -
    - {map(preview.request_headers, (value, key) => { - return ( -

    - {key}: {value} -

    - ); - })} -
    +
    +

    + +

    +
    +
    + {map(preview.request_headers, (value, key) => { + return ( +

    + {key}: {value} +

    + ); + })}
    +
    -
    -

    - -

    -
    -
    - -
    +
    +

    + +

    +
    +
    +
    +
    -
    -

    - -

    -
    +

    + +

    +
    +
    + {map(preview.response_headers, (value, key) => { + return ( +

    + {key}: {value} +

    + ); + })}
    - +
    + + ); + }; + + const apiRecordView = () => { + return ( + <> +
    + API Response + +
    + + ); + }; + + return ( + <> + {loading && ( +
    +
    + Loading... +
    +
    )} + {!loading && view == "accordion" && accordionView()} + {!loading && view == "apiRecord" && apiRecordView()} ); }; diff --git a/app/frontend/js/apps/ExtractionApp/components/PreviewModal.jsx b/app/frontend/js/apps/ExtractionApp/components/PreviewModal.jsx index 705de85a..3b971ac1 100644 --- a/app/frontend/js/apps/ExtractionApp/components/PreviewModal.jsx +++ b/app/frontend/js/apps/ExtractionApp/components/PreviewModal.jsx @@ -21,10 +21,10 @@ const PreviewModal = ({ "col-12": !appDetails.extractionDefinition.paginated, }); - const handleSampleClick = async (type) => { + const handleSampleClick = async () => { const response = await request .post( - `/pipelines/${appDetails.pipeline.id}/harvest_definitions/${appDetails.harvestDefinition.id}/extraction_definitions/${appDetails.extractionDefinition.id}/extraction_jobs.json?kind=sample&type=${type}` + `/pipelines/${appDetails.pipeline.id}/harvest_definitions/${appDetails.harvestDefinition.id}/extraction_definitions/${appDetails.extractionDefinition.id}/extraction_jobs.json?kind=sample` ) .then((response) => { return response; @@ -50,27 +50,17 @@ const PreviewModal = ({ handleClose(); }} > - - Continue editing extraction definition - - -
    diff --git a/app/frontend/js/apps/TransformationApp/components/Field.jsx b/app/frontend/js/apps/TransformationApp/components/Field.jsx index 24a347a6..6be55e1c 100644 --- a/app/frontend/js/apps/TransformationApp/components/Field.jsx +++ b/app/frontend/js/apps/TransformationApp/components/Field.jsx @@ -15,7 +15,10 @@ import { selectAppDetails, clickedOnRunFields, } from "~/js/features/TransformationApp/AppDetailsSlice"; -import { toggleDisplayField } from "~/js/features/TransformationApp/UiFieldsSlice"; +import { + toggleDisplayField, + setActiveField, +} from "~/js/features/TransformationApp/UiFieldsSlice"; import { selectRawRecord } from "/js/features/TransformationApp/RawRecordSlice"; @@ -31,7 +34,7 @@ const Field = ({ id }) => { const rawRecord = useSelector(selectRawRecord); - const { saved, deleting, saving, running, error, hasRun, displayed } = + const { saved, deleting, saving, running, error, hasRun, displayed, active } = useSelector((state) => selectUiFieldById(state, id)); const dispatch = useDispatch(); @@ -125,7 +128,14 @@ const Field = ({ id }) => { } }, []); - const fieldClasses = classNames("col-12", "collapse", { show: displayed }); + const fieldClasses = classNames("col-12", "collapse", { + show: displayed, + "border-primary": active, + }); + + const cardClasses = classNames("card", "border", "rounded", { + "border-primary": active, + }); const handleClose = () => setShowModal(false); const handleShow = () => setShowModal(true); @@ -137,8 +147,13 @@ const Field = ({ id }) => { return ( <> -
    -
    +
    dispatch(setActiveField(id))} + > +
    diff --git a/app/frontend/js/apps/TransformationApp/components/FieldNavigationListItem.jsx b/app/frontend/js/apps/TransformationApp/components/FieldNavigationListItem.jsx index fe9d249a..7e5c51ee 100644 --- a/app/frontend/js/apps/TransformationApp/components/FieldNavigationListItem.jsx +++ b/app/frontend/js/apps/TransformationApp/components/FieldNavigationListItem.jsx @@ -4,6 +4,7 @@ import { selectFieldById } from "~/js/features/TransformationApp/FieldsSlice"; import { selectUiFieldById, toggleDisplayField, + setActiveField, } from "~/js/features/TransformationApp/UiFieldsSlice"; import classNames from "classnames"; @@ -26,14 +27,22 @@ const FieldNavigationListItem = ({ id }) => { return "condition"; }; + const handleListItemClick = () => { + const desiredDisplaySetting = !displayed; + + dispatch(toggleDisplayField({ id: id, displayed: desiredDisplaySetting })); + + if (desiredDisplaySetting == true) { + dispatch(setActiveField(id)); + } + }; + return (
  • - dispatch(toggleDisplayField({ id: id, displayed: !displayed })) - } + onClick={() => handleListItemClick()} href={`#field-${id}`} role="button" aria-expanded={displayed} diff --git a/app/frontend/js/components/CodeEditor.jsx b/app/frontend/js/components/CodeEditor.jsx index 774c7527..5190f846 100644 --- a/app/frontend/js/components/CodeEditor.jsx +++ b/app/frontend/js/components/CodeEditor.jsx @@ -46,8 +46,6 @@ const CodeEditor = ({ initContent, onChange, format = "ruby", ...props }) => { return content; } else if (format == "XML") { return xmlFormat(initContent, { indentation: " ", lineSeparator: "\n" }); - } else if (format == "ruby") { - return initContent; } else { return initContent; } diff --git a/app/frontend/js/features/ExtractionApp/RequestsSlice.js b/app/frontend/js/features/ExtractionApp/RequestsSlice.js index 85c1f0ef..05db70b7 100644 --- a/app/frontend/js/features/ExtractionApp/RequestsSlice.js +++ b/app/frontend/js/features/ExtractionApp/RequestsSlice.js @@ -19,12 +19,14 @@ export const previewRequest = createAsyncThunk( harvestDefinitionId, extractionDefinitionId, previousRequestId, + page, + record, } = payload; - let path = `/pipelines/${pipelineId}/harvest_definitions/${harvestDefinitionId}/extraction_definitions/${extractionDefinitionId}/requests/${id}`; + let path = `/pipelines/${pipelineId}/harvest_definitions/${harvestDefinitionId}/extraction_definitions/${extractionDefinitionId}/requests/${id}?page=${page}&record=${record}`; if (previousRequestId != undefined) { - path = `${path}?previous_request_id=${previousRequestId}`; + path = `${path}&previous_request_id=${previousRequestId}`; } const response = request.get(path).then((response) => { diff --git a/app/frontend/js/features/ExtractionApp/UiAppDetailsSlice.js b/app/frontend/js/features/ExtractionApp/UiAppDetailsSlice.js index ea1ef7b5..02bc1286 100644 --- a/app/frontend/js/features/ExtractionApp/UiAppDetailsSlice.js +++ b/app/frontend/js/features/ExtractionApp/UiAppDetailsSlice.js @@ -8,7 +8,7 @@ const uiAppDetailsSlice = createSlice({ state.activeRequest = action.payload; state.sharedDefinitionsTabActive = false; }, - activateSharedDefinitionsTab(state, action) { + activateSharedDefinitionsTab(state) { state.activeRequest = 0; state.sharedDefinitionsTabActive = true; }, diff --git a/app/frontend/js/features/TransformationApp/UiFieldsSlice.js b/app/frontend/js/features/TransformationApp/UiFieldsSlice.js index 68fe9aec..0fc3a490 100644 --- a/app/frontend/js/features/TransformationApp/UiFieldsSlice.js +++ b/app/frontend/js/features/TransformationApp/UiFieldsSlice.js @@ -15,6 +15,19 @@ const uiFieldsSlice = createSlice({ changes: { displayed: action.payload.displayed }, }); }, + setActiveField(state, action) { + uiFieldsAdapter.updateMany( + state, + state.ids.map((id) => { + return { id: id, changes: { active: false } }; + }) + ); + + uiFieldsAdapter.updateOne(state, { + id: action.payload, + changes: { active: true }, + }); + }, toggleDisplayFields(state, action) { const { fields, displayed } = action.payload; @@ -29,6 +42,13 @@ const uiFieldsSlice = createSlice({ extraReducers: (builder) => { builder .addCase(addField.fulfilled, (state, action) => { + uiFieldsAdapter.updateMany( + state, + state.ids.map((id) => { + return { id: id, changes: { active: false } }; + }) + ); + uiFieldsAdapter.upsertOne(state, { id: action.payload.id, saved: true, @@ -38,6 +58,7 @@ const uiFieldsSlice = createSlice({ hasRun: false, expanded: true, displayed: true, + active: true, }); }) .addCase(clickedOnRunFields.pending, (state, action) => { @@ -117,6 +138,7 @@ export const selectDisplayedFieldIds = (state) => { .map((field) => field.id); }; -export const { toggleDisplayField, toggleDisplayFields } = actions; +export const { toggleDisplayField, toggleDisplayFields, setActiveField } = + actions; export default reducer; diff --git a/app/frontend/js/formModals.js b/app/frontend/js/formModals.js index 98327c98..77891b77 100644 --- a/app/frontend/js/formModals.js +++ b/app/frontend/js/formModals.js @@ -112,6 +112,13 @@ if (transformationDefinitionSettingsForms) { true, results ); + }, + () => { + document.getElementById( + `js-record-selector-result-${id}` + ).innerHTML = `

    + Record Selector did not return anything +

    `; } ); } diff --git a/app/frontend/stylesheets/bootstrap/_alert.scss b/app/frontend/stylesheets/bootstrap/_alert.scss index 164011dc..2147a4a4 100644 --- a/app/frontend/stylesheets/bootstrap/_alert.scss +++ b/app/frontend/stylesheets/bootstrap/_alert.scss @@ -1,6 +1,7 @@ .alert { right: 1rem; left: auto; + --bs-alert-color: #{$white} } .alert-danger { diff --git a/app/models/extraction_definition.rb b/app/models/extraction_definition.rb index bcc7874c..33ed7a4a 100644 --- a/app/models/extraction_definition.rb +++ b/app/models/extraction_definition.rb @@ -51,7 +51,6 @@ class ExtractionDefinition < ApplicationRecord with_options presence: true, if: :enrichment? do validates :destination_id validates :source_id - validates :enrichment_url end def total_selector_format diff --git a/app/models/parameter.rb b/app/models/parameter.rb index 471f2111..bd7cc8d7 100644 --- a/app/models/parameter.rb +++ b/app/models/parameter.rb @@ -20,7 +20,6 @@ def incremental_evaluation(response_object) # rubocop:disable Lint/UnusedBlockArgument # rubocop:disable Security/Eval - # rubocop:disable Lint/SuppressedException def dynamic_evaluation(response_object) block = ->(response) { eval(content) } @@ -29,10 +28,13 @@ def dynamic_evaluation(response_object) content: block.call(response_object&.body) ) rescue StandardError + Parameter.new( + name:, + content: "#{content}-evaluation-error".parameterize + ) end # rubocop:enable Lint/UnusedBlockArgument # rubocop:enable Security/Eval - # rubocop:enable Lint/SuppressedException def to_h return if slug? diff --git a/app/models/request.rb b/app/models/request.rb index a686f5d6..b51bfc5d 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -42,9 +42,13 @@ def to_h } end + def first_request? + extraction_definition.requests.first.id == id + end + private - def slug(response) + def slug(response = nil) parameters .slug .map { |parameter| parameter.evaluate(response) } diff --git a/app/supplejack/extraction/api_record.rb b/app/supplejack/extraction/api_record.rb new file mode 100644 index 00000000..ef19f179 --- /dev/null +++ b/app/supplejack/extraction/api_record.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# This class is for normalizing data received from the API +# so that it can be treated the same as a response from our HTTP Client + +module Extraction + class ApiRecord + attr_reader :body + + def initialize(body) + @body = body + end + + def [](index) + @body[index] + end + + def to_hash + { + body: body.to_json + } + end + end +end diff --git a/app/supplejack/extraction/api_response.rb b/app/supplejack/extraction/api_response.rb new file mode 100644 index 00000000..0d69efe1 --- /dev/null +++ b/app/supplejack/extraction/api_response.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Extraction + class ApiResponse + def initialize(response) + @response = response + end + + def record(index) + ApiRecord.new(JSON.parse(@response.body)['records'][index]) + end + end +end diff --git a/app/supplejack/extraction/enrichment_execution.rb b/app/supplejack/extraction/enrichment_execution.rb index c9acb34b..8f1aa012 100644 --- a/app/supplejack/extraction/enrichment_execution.rb +++ b/app/supplejack/extraction/enrichment_execution.rb @@ -52,7 +52,8 @@ def page_from_index(index) end def new_enrichment_extraction(api_record, page) - EnrichmentExtraction.new(@extraction_definition, api_record, page, @extraction_job.extraction_folder) + EnrichmentExtraction.new(@extraction_definition.requests.last, ApiRecord.new(api_record), page, + @extraction_job.extraction_folder) end def enqueue_record_transformation(api_record, document, page) diff --git a/app/supplejack/extraction/enrichment_extraction.rb b/app/supplejack/extraction/enrichment_extraction.rb index beebbdff..be582c01 100644 --- a/app/supplejack/extraction/enrichment_extraction.rb +++ b/app/supplejack/extraction/enrichment_extraction.rb @@ -2,36 +2,33 @@ module Extraction class EnrichmentExtraction < AbstractExtraction - def initialize(extraction_definition, record, page, extraction_folder = nil) + def initialize(request, record, page = 1, extraction_folder = nil) super() - @extraction_definition = extraction_definition + @request = request + @extraction_definition = request.extraction_definition @record = record @page = page @extraction_folder = extraction_folder end def valid? - block(expression).call(@record).present? + url.exclude?('evaluation-error') end private - def params; end - - def expression - @extraction_definition.enrichment_url.match(/.+\#{(?.+)}/)[:expression] + def url + @request.url(@record) end - # rubocop:disable Lint/UnusedBlockArgument - # rubocop:disable Security/Eval - def block(code) - ->(record) { eval(code) } + def params + @request.query_parameters(@record) end - # rubocop:enable Security/Eval - # rubocop:enable Lint/UnusedBlockArgument - def url - block(@extraction_definition.enrichment_url).call(@record) + def headers + return super if @request.headers.blank? + + super.merge(@request.headers(@response)) end def file_path diff --git a/app/supplejack/extraction/record_extraction.rb b/app/supplejack/extraction/record_extraction.rb index 4871122d..11bc311b 100644 --- a/app/supplejack/extraction/record_extraction.rb +++ b/app/supplejack/extraction/record_extraction.rb @@ -2,25 +2,25 @@ module Extraction class RecordExtraction < AbstractExtraction - def initialize(extraction_definition, page, harvest_job = nil) + def initialize(request, page, harvest_job = nil) super() - @extraction_definition = extraction_definition - @api_source = extraction_definition.destination - @page = page - @harvest_job = harvest_job + @request = request + @extraction_definition = request.extraction_definition + @page = page + @harvest_job = harvest_job end private def url - "#{@api_source.url}/harvester/records" + "#{api_source.url}/harvester/records" end def params { search: active_filter.merge(fragment_filter), search_options: { page: @page }, - api_key: @api_source.api_key + api_key: api_source.api_key } end @@ -35,5 +35,11 @@ def fragment_filter { 'fragments.source_id' => @extraction_definition.source_id } end end + + def api_source + return @harvest_job.pipeline_job.destination if @harvest_job.present? + + @extraction_definition.destination + end end end diff --git a/app/supplejack/extraction/sj_api_enrichment_iterator.rb b/app/supplejack/extraction/sj_api_enrichment_iterator.rb index 172ad607..6fa7d3ae 100644 --- a/app/supplejack/extraction/sj_api_enrichment_iterator.rb +++ b/app/supplejack/extraction/sj_api_enrichment_iterator.rb @@ -31,7 +31,7 @@ def find_max_page(record_extraction) def get_api_document(page) RecordExtraction.new( - @extraction_definition, page, @harvest_job + @extraction_definition.requests.first, page, @harvest_job ).extract end end diff --git a/app/supplejack/transformation/raw_records_extractor.rb b/app/supplejack/transformation/raw_records_extractor.rb index 63fd7776..76f97247 100644 --- a/app/supplejack/transformation/raw_records_extractor.rb +++ b/app/supplejack/transformation/raw_records_extractor.rb @@ -38,7 +38,10 @@ def format end def record_selector - @transformation_definition.record_selector || '*' + return @transformation_definition.record_selector if @transformation_definition.record_selector.present? + return '*' if format == 'JSON' + + '/' end end end diff --git a/app/views/extraction_definitions/_create_edit_enrichment_modal.html.erb b/app/views/extraction_definitions/_create_edit_enrichment_modal.html.erb new file mode 100644 index 00000000..bf014d32 --- /dev/null +++ b/app/views/extraction_definitions/_create_edit_enrichment_modal.html.erb @@ -0,0 +1,100 @@ +<% id ||= 'create-modal' %> +<% modal_heading ||= 'Create extraction definition' %> +<% modal_subheading ||= '' %> +<% model ||= ExtractionDefinition.new %> +<% confirmation_button_text ||= 'Create enrichment extraction' %> + +<%= tag.div( + class: 'modal fade', + id:, + tabindex: '-1', + 'aria-labelledby': 'Create', + 'aria-hidden': 'true' + ) do %> + +<% end %> diff --git a/app/views/extraction_definitions/_create_edit_modal.html.erb b/app/views/extraction_definitions/_create_edit_harvest_modal.html.erb similarity index 100% rename from app/views/extraction_definitions/_create_edit_modal.html.erb rename to app/views/extraction_definitions/_create_edit_harvest_modal.html.erb diff --git a/app/views/extraction_definitions/_enrichment_form.html.erb b/app/views/extraction_definitions/_enrichment_form.html.erb deleted file mode 100644 index a3277a62..00000000 --- a/app/views/extraction_definitions/_enrichment_form.html.erb +++ /dev/null @@ -1,73 +0,0 @@ -<%= vertical_form_with model: [@pipeline, @harvest_definition, @extraction_definition], - id: 'js-extraction-definition-form' do |form| %> - <%= form.hidden_field :pipeline_id, value: @pipeline.id %> - <%= form.hidden_field :kind, value: 'enrichment' %> - -
    -
    -
    - <% if @extraction_definition.name.present? %> -
    - <%= form.label :name, 'Name', class: 'form-label' %> -
    - -
    - <%= form.text_field :name, class: 'form-control' %> -
    - <% end %> - -
    - <%= form.label :destination_id, 'Enrichment Target', class: 'form-label' %> -
    - -
    - <%= form.select :destination_id, options_from_collection_for_select(@destinations, 'id', 'name'), {}, - class: 'form-select' %> -
    - -
    - <%= form.label :source_id, 'Enrichment Target Source ID', class: 'form-label' %> -
    -
    - <%= form.text_field :source_id, class: 'form-control' %> -
    - -
    - <%= form.label :enrichment_url, 'Enrichment URL', class: 'form-label' %> -
    -
    - <%= form.hidden_field :enrichment_url, class: 'form-control', id: 'js-enrichment-url' %> -
    -
    - -
    - <%= form.label :format, class: 'form-label' do %> - Format - - <% end %> -
    -
    - <% options = find_options_from_validations(ExtractionDefinition, :format) %> - <%= form.select :format, options, {}, class: 'form-select' %> -
    - - <%= form.hidden_field :page, value: 1 %> - <%= form.hidden_field :per_page, value: 20 %> - <%= form.hidden_field :total_selector, value: '$.meta.total_pages' %> - -
    - <%= form.label :throttle, 'Enrichment Throttle (in ms)', class: 'form-label' %> -
    -
    - <%= form.number_field :throttle, class: 'form-control' %> -
    -
    -
    -
    - -<% end %> - -
    -
    diff --git a/app/views/extraction_definitions/edit.html.erb b/app/views/extraction_definitions/edit.html.erb deleted file mode 100644 index 7cf13f96..00000000 --- a/app/views/extraction_definitions/edit.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<%= content_for(:title) { "Editing #{@extraction_definition.name_in_database}" } %> - -<%= content_for(:header) do %> - <%= render 'shared/breadcrumb_nav' %> - -

    <%= @extraction_definition.name_in_database %>

    - -
    - - <% if @extraction_definition.enrichment? %> - - - - <% end %> - - - - <%= link_to 'Cancel', pipeline_path(@pipeline), class: 'btn btn-outline-danger' %> -
    - -
    -<% end %> - -<%= render "#{@extraction_definition.kind}_form" %> diff --git a/app/views/extraction_definitions/new.html.erb b/app/views/extraction_definitions/new.html.erb deleted file mode 100644 index a6355dab..00000000 --- a/app/views/extraction_definitions/new.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<%= content_for(:title) { 'New Extraction Definition' } %> - -<%= content_for :header do %> - <%= render 'shared/breadcrumb_nav' %> - -

    New <%= @extraction_definition.kind %> extraction

    - -
    - - <% if @extraction_definition.enrichment? %> - - - - <% end %> - - - - <%= link_to 'Cancel', pipeline_path(@pipeline), class: 'btn btn-outline-danger' %> -
    - -
    -<% end %> - -<%= render "#{@extraction_definition.kind}_form" %> diff --git a/app/views/extraction_definitions/show.html.erb b/app/views/extraction_definitions/show.html.erb index f7cee985..a89988b1 100644 --- a/app/views/extraction_definitions/show.html.erb +++ b/app/views/extraction_definitions/show.html.erb @@ -24,12 +24,22 @@
    -<%= render 'create_edit_modal', - id: 'update-extraction-definition-modal', - model: @extraction_definition, - modal_heading: 'Edit extraction definition', - modal_subheading: 'Define the settings for your extraction definition', - confirmation_button_text: 'Update extraction definition' %> +<% if @harvest_definition.harvest? %> + <%= render 'create_edit_harvest_modal', + id: 'update-extraction-definition-modal', + model: @extraction_definition, + modal_heading: 'Edit extraction definition', + modal_subheading: 'Define the settings for your extraction definition', + confirmation_button_text: 'Update extraction definition' %> +<% else %> + <%= render 'create_edit_enrichment_modal', + id: 'update-extraction-definition-modal', + model: @extraction_definition, + enrichment_definition: @harvest_definition, + modal_heading: 'Edit extraction definition', + modal_subheading: 'Define the settings for your extraction definition', + confirmation_button_text: 'Update extraction definition' %> +<% end %> <%= render layout: 'shared/delete_modal', locals: { path: pipeline_harvest_definition_extraction_definition_path(@pipeline, @harvest_definition, diff --git a/app/views/pipelines/_enrichment_definition.html.erb b/app/views/pipelines/_enrichment_definition.html.erb index bb324e02..682809ac 100644 --- a/app/views/pipelines/_enrichment_definition.html.erb +++ b/app/views/pipelines/_enrichment_definition.html.erb @@ -51,7 +51,7 @@ block_definition: enrichment_definition, definition: enrichment_definition.extraction_definition, edit_path: - edit_pipeline_harvest_definition_extraction_definition_path( + pipeline_harvest_definition_extraction_definition_path( @pipeline, enrichment_definition, enrichment_definition.extraction_definition @@ -122,13 +122,13 @@ or
  • - <%= link_to '+ Add new extraction definition', - new_pipeline_harvest_definition_extraction_definition_path( - @pipeline, - enrichment_definition, - kind: 'enrichment' - ), - class: 'btn btn-primary' %> +
    <% end %> @@ -239,3 +239,7 @@
    + +<%= render partial: 'extraction_definitions/create_edit_enrichment_modal', + locals: { enrichment_definition:, + id: "create-enrichment-extraction-definition-modal-#{enrichment_definition.id}" } %> diff --git a/app/views/pipelines/_harvest_definition.html.erb b/app/views/pipelines/_harvest_definition.html.erb index d7f2f104..941d6869 100644 --- a/app/views/pipelines/_harvest_definition.html.erb +++ b/app/views/pipelines/_harvest_definition.html.erb @@ -161,3 +161,19 @@ + +<%= render 'extraction_definitions/create_edit_harvest_modal', + { + id: 'create-harvest-extraction-definition-modal', + modal_heading: 'Create extraction definition', + modal_subheading: 'Define the settings for your extraction definition' + } %> + + <%= render 'transformation_definitions/create_edit_modal', + { + id: 'create-harvest-transformation-definition-modal', + modal_heading: 'Create transformation definition', + harvest_definition: @harvest_definition, + modal_subheading: 'Define the settings for your transformation definition', + extraction_jobs: @extraction_jobs + } %> diff --git a/app/views/pipelines/show.html.erb b/app/views/pipelines/show.html.erb index b93770a6..0fdce5e5 100644 --- a/app/views/pipelines/show.html.erb +++ b/app/views/pipelines/show.html.erb @@ -168,39 +168,41 @@ <% end %> -<%= render layout: 'shared/create_modal', - locals: { modal_heading: 'Add enrichment', id: 'add-enrichment', - modal_subheading: 'Add a source_id to identify records from this enrichment' } do %> -
    - <%= vertical_form_with model: [@pipeline, @enrichment_definition] do |form| %> -
    - <%= form.hidden_field :pipeline_id, value: @pipeline.id %> - <%= form.hidden_field :kind, value: 'enrichment' %> - <%= form.hidden_field :priority, value: "-#{@pipeline.enrichments.count + 1}" %> - -
    - <%= form.label :source_id, class: 'form-label' do %> - Source ID - - - <% end %> -
    -
    - <%= form.text_field :source_id, - class: { - 'form-control': true, - 'is-invalid': @enrichment_definition.errors[:source_id].any? - } %> +<% if @pipeline.harvest.present? %> + <%= render layout: 'shared/create_modal', + locals: { modal_heading: 'Add enrichment', id: 'add-enrichment', + modal_subheading: 'Add a source_id to identify records from this enrichment' } do %> +
    + <%= vertical_form_with model: [@pipeline, @enrichment_definition] do |form| %> +
    + <%= form.hidden_field :pipeline_id, value: @pipeline.id %> + <%= form.hidden_field :kind, value: 'enrichment' %> + <%= form.hidden_field :priority, value: @pipeline.harvest_definitions.last.priority - 1 %> + +
    + <%= form.label :source_id, class: 'form-label' do %> + Source ID + + + <% end %> +
    +
    + <%= form.text_field :source_id, + class: { + 'form-control': true, + 'is-invalid': @enrichment_definition.errors[:source_id].any? + } %> +
    -
    -
    - -
    - <% end %> -
    +
    + +
    + <% end %> +
    + <% end %> <% end %> <% if @harvest_definition.persisted? %> @@ -238,13 +240,6 @@
    <% end %> - <%= render 'extraction_definitions/create_edit_modal', - { - id: 'create-harvest-extraction-definition-modal', - modal_heading: 'Create extraction definition', - modal_subheading: 'Define the settings for your extraction definition' - } %> - <%= render( layout: 'shared/create_modal', locals: { @@ -277,13 +272,4 @@ <% end %> - - <%= render 'transformation_definitions/create_edit_modal', - { - id: 'create-harvest-transformation-definition-modal', - modal_heading: 'Create transformation definition', - harvest_definition: @harvest_definition, - modal_subheading: 'Define the settings for your transformation definition', - extraction_jobs: @extraction_jobs - } %> <% end %> diff --git a/app/views/transformation_definitions/show.html.erb b/app/views/transformation_definitions/show.html.erb index d950eef6..f72eef3c 100644 --- a/app/views/transformation_definitions/show.html.erb +++ b/app/views/transformation_definitions/show.html.erb @@ -37,7 +37,7 @@ <% end %> -<%= render 'create_edit_modal', +<%= render 'transformation_definitions/create_edit_modal', { id: 'update-transformation-definition-modal', model: @transformation_definition, diff --git a/config/routes.rb b/config/routes.rb index f2e68156..15e82a8b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,13 +29,7 @@ resources :schedules resources :harvest_definitions, only: %i[create update destroy] do - resources :extraction_definitions, only: %i[show create update destroy new edit] do - collection do - post :test - post :test_record_extraction - post :test_enrichment_extraction - end - + resources :extraction_definitions, only: %i[show create update destroy] do member do post :clone end diff --git a/db/migrate/20231016002448_remove_enrichment_url_from_extraction_definition.rb b/db/migrate/20231016002448_remove_enrichment_url_from_extraction_definition.rb new file mode 100644 index 00000000..9a97a3a0 --- /dev/null +++ b/db/migrate/20231016002448_remove_enrichment_url_from_extraction_definition.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RemoveEnrichmentUrlFromExtractionDefinition < ActiveRecord::Migration[7.0] + def change + remove_column :extraction_definitions, :enrichment_url, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 6d2b7da7..c4a3799d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_10_09_033947) do +ActiveRecord::Schema[7.0].define(version: 2023_10_16_002448) do create_table "destinations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "url", null: false @@ -30,7 +30,6 @@ t.datetime "updated_at", null: false t.integer "kind", default: 0 t.string "source_id" - t.string "enrichment_url" t.bigint "destination_id" t.integer "page", default: 1 t.string "total_selector" diff --git a/spec/factories/extraction_definition.rb b/spec/factories/extraction_definition.rb index f264d8a9..6dc566f5 100644 --- a/spec/factories/extraction_definition.rb +++ b/spec/factories/extraction_definition.rb @@ -29,10 +29,9 @@ trait :enrichment do kind { 1 } source_id { 'test' } - # rubocop:disable Lint/InterpolationCheck - enrichment_url { '"https://api.figshare.com/v1/articles/#{record["dc_identifier"].first}"' } - # rubocop:enable Lint/InterpolationCheck - throttle { 1000 } + base_url { 'https://api.figshare.com/v1/articles' } + total_selector { '$.meta.total_pages' } + per_page { 20 } end pipeline diff --git a/spec/models/extraction_definition_spec.rb b/spec/models/extraction_definition_spec.rb index ab48bb4d..bd6c24cb 100644 --- a/spec/models/extraction_definition_spec.rb +++ b/spec/models/extraction_definition_spec.rb @@ -22,7 +22,6 @@ it { is_expected.not_to validate_presence_of(:destination_id).with_message("can't be blank") } it { is_expected.not_to validate_presence_of(:source_id).with_message("can't be blank") } - it { is_expected.not_to validate_presence_of(:enrichment_url).with_message("can't be blank") } end context 'when the extraction definition is for an enrichment' do @@ -33,7 +32,6 @@ it { is_expected.to validate_presence_of(:throttle).with_message('is not a number') } it { is_expected.to validate_presence_of(:destination_id).with_message("can't be blank") } it { is_expected.to validate_presence_of(:source_id).with_message("can't be blank") } - it { is_expected.to validate_presence_of(:enrichment_url).with_message("can't be blank") } it { is_expected.not_to validate_presence_of(:format).with_message("can't be blank") } it { is_expected.not_to validate_presence_of(:base_url).with_message("can't be blank") } diff --git a/spec/models/parameter_spec.rb b/spec/models/parameter_spec.rb index 103fdb1b..852d6483 100644 --- a/spec/models/parameter_spec.rb +++ b/spec/models/parameter_spec.rb @@ -68,6 +68,10 @@ create(:parameter, kind: 'query', name: 'itemsPerPage', content: 'JSON.parse(response)["items_found"] + 10', content_type: 1) end + let(:erroring_dynamic_response) do + create(:parameter, kind: 'query', name: 'itemsPerPage', content: 'raise', + content_type: 1) + end let(:extraction_definition) { create(:extraction_definition, :figshare) } let(:request) { create(:request, :figshare_initial_request, extraction_definition:) } let(:response) { Extraction::DocumentExtraction.new(request).extract } @@ -91,5 +95,9 @@ it 'returns the incremented parameter if it is incremental' do expect(incremental.evaluate(response).content).to eq '22' end + + it 'returns a helpful message if the paramater has failed to be evaluated' do + expect(erroring_dynamic_response.evaluate(response).content).to eq 'raise-evaluation-error' + end end end diff --git a/spec/requests/extraction_definitions_spec.rb b/spec/requests/extraction_definitions_spec.rb index 3a31cc0e..0827b127 100644 --- a/spec/requests/extraction_definitions_spec.rb +++ b/spec/requests/extraction_definitions_spec.rb @@ -12,14 +12,6 @@ sign_in user end - describe '#new' do - it 'renders the new form' do - get new_pipeline_harvest_definition_extraction_definition_path(pipeline, harvest_definition, kind: 'enrichment') - - expect(response).to have_http_status :ok - end - end - describe '#create' do context 'with valid parameters' do let(:extraction_definition2) { build(:extraction_definition, pipeline:) } @@ -155,55 +147,6 @@ end end - describe '#test_record_extraction' do - let(:destination) { create(:destination) } - let(:extraction_definition) { create(:extraction_definition, :enrichment, destination:) } - - before do - stub_figshare_enrichment_page1(destination) - end - - it 'returns a document extraction of API records' do - post test_record_extraction_pipeline_harvest_definition_extraction_definitions_path(pipeline, harvest_definition), params: { - extraction_definition: extraction_definition.attributes - } - - expect(response).to have_http_status :ok - - json_response = response.parsed_body['body'] - records = JSON.parse(json_response)['records'] - - records.each do |record| - expect(record).to have_key('dc_identifier') - expect(record).to have_key('internal_identifier') - end - end - end - - describe '#test_enrichment_extraction' do - let(:destination) { create(:destination) } - let(:ed) { create(:extraction_definition, :enrichment, destination:) } - - before do - stub_figshare_enrichment_page1(destination) - end - - it 'returns a document extraction of data for an enrichment' do - post test_enrichment_extraction_pipeline_harvest_definition_extraction_definitions_path(pipeline, harvest_definition), params: { - extraction_definition: ed.attributes - } - - expect(response).to have_http_status :ok - - json_response = response.parsed_body['body'] - records = JSON.parse(json_response)['items'] - - records.each do |record| - expect(record).to have_key('article_id') - end - end - end - describe '#clone' do let!(:extraction_definition) { create(:extraction_definition, name: 'one') } let!(:request_one) { create(:request, :figshare_initial_request, extraction_definition:) } diff --git a/spec/requests/extraction_jobs_spec.rb b/spec/requests/extraction_jobs_spec.rb index 719b9566..05ba2d09 100644 --- a/spec/requests/extraction_jobs_spec.rb +++ b/spec/requests/extraction_jobs_spec.rb @@ -91,49 +91,31 @@ end context 'when the format is JSON' do - context 'when the type is pipeline' do - it 'returns information to redirect to the pipeline path' do - post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'pipeline', format: 'json') + context 'when there is allready a Transformation Definition associated with the harvest_definition' do + it 'updates the Transformation Definition to reference the new job id' do + existing_extraction_job = harvest_definition.transformation_definition.extraction_job - body = JSON.parse(response.body) + post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'transform', format: 'json') - expect(body['location']).to eq "/pipelines/#{pipeline.id}" - end - - it 'queues a job' do - expect(ExtractionWorker).to receive(:perform_async) - - post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'pipeline', format: 'json') + harvest_definition.reload + + expect(harvest_definition.transformation_definition.extraction_job).not_to eq existing_extraction_job end end - context 'when the type is transform' do - context 'when there is allready a Transformation Definition associated with the harvest_definition' do - it 'updates the Transformation Definition to reference the new job id' do - existing_extraction_job = harvest_definition.transformation_definition.extraction_job + context 'when there is no Transformation Definition associated with the harvest definition' do + it 'creates a new Transformation Definition and assigns it to the Harvest Definition' do + harvest_definition.transformation_definition.destroy + harvest_definition.reload - post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'transform', format: 'json') + expect(harvest_definition.transformation_definition).to be_nil - harvest_definition.reload - - expect(harvest_definition.transformation_definition.extraction_job).not_to eq existing_extraction_job - end - end - - context 'when there is no Transformation Definition associated with the harvest definition' do - it 'creates a new Transformation Definition and assigns it to the Harvest Definition' do - harvest_definition.transformation_definition.destroy - harvest_definition.reload - - expect(harvest_definition.transformation_definition).to be_nil - - expect do - post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'transform', format: 'json') - end.to change(TransformationDefinition, :count).by(1) + expect do + post pipeline_harvest_definition_extraction_definition_extraction_jobs_path(pipeline, harvest_definition, extraction_definition, kind: 'full', type: 'transform', format: 'json') + end.to change(TransformationDefinition, :count).by(1) - harvest_definition.reload - expect(harvest_definition.transformation_definition).not_to be_nil - end + harvest_definition.reload + expect(harvest_definition.transformation_definition).not_to be_nil end end end diff --git a/spec/requests/requests_spec.rb b/spec/requests/requests_spec.rb index 46bc64a3..2171957c 100644 --- a/spec/requests/requests_spec.rb +++ b/spec/requests/requests_spec.rb @@ -44,44 +44,103 @@ end end - describe 'GET /show' do - before do - stub_figshare_harvest_requests(request_one) + describe 'GET /show' do + context 'when the extraction definition is for a harvest' do + before do + stub_figshare_harvest_requests(request_one) + end + + let(:request_one) { create(:request, :figshare_initial_request, extraction_definition:) } + let(:request_two) { create(:request, :figshare_main_request, extraction_definition:) } + + it 'returns a JSON response of the completed request' do + get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, + extraction_definition, request_one) + + expect(response).to have_http_status :ok + + json_data = response.parsed_body + + expected_keys = %w[url format preview http_method created_at updated_at id] + + expected_keys.each do |key| + expect(json_data).to have_key(key) + end + end + + it 'returns a JSON response of the completed request referencing a response' do + get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, + extraction_definition, request_two, previous_request_id: request_one.id) + + expect(response).to have_http_status :ok + + json_data = response.parsed_body + + expected_keys = %w[url format preview http_method created_at updated_at id] + + expected_keys.each do |key| + expect(json_data).to have_key(key) + end + + expect(JSON.parse(json_data['preview']['body'])['page_nr']).to eq 2 + end end - let(:request_one) { create(:request, :figshare_initial_request, extraction_definition:) } - let(:request_two) { create(:request, :figshare_main_request, extraction_definition:) } + context 'when the extraction definition is for an enrichment' do + let(:destination) { create(:destination) } + let(:extraction_definition) { create(:extraction_definition, :enrichment, pipeline:, destination:) } - it 'returns a JSON response of the completed request' do - get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, - extraction_definition, request_one) + let!(:request_one) { create(:request, extraction_definition:) } + let!(:request_two) { create(:request, extraction_definition:) } - expect(response).to have_http_status :ok + let!(:parameter) { create(:parameter, content: "response['dc_identifier'].first", kind: 'slug', request: request_two, content_type: 'dynamic') } - json_data = response.parsed_body + before do + stub_figshare_enrichment_page1(destination) + end - expected_keys = %w[url format preview http_method created_at updated_at id] + it 'returns a JSON response of data from the API' do + get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, + extraction_definition, request_one) - expected_keys.each do |key| - expect(json_data).to have_key(key) - end - end + expect(response).to have_http_status :ok + + json_data = response.parsed_body - it 'returns a JSON response of the completed request referencing a response' do - get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, - extraction_definition, request_two, previous_request_id: request_one.id) + expected_keys = %w[url format preview http_method created_at updated_at id] - expect(response).to have_http_status :ok + expected_keys.each do |key| + expect(json_data).to have_key(key) + end - json_data = response.parsed_body + expected_preview_keys = %w[page total_pages total_records body] - expected_keys = %w[url format preview http_method created_at updated_at id] + preview_data = json_data['preview'] - expected_keys.each do |key| - expect(json_data).to have_key(key) + expected_preview_keys.each do |key| + expect(preview_data).to have_key(key) + end end - expect(JSON.parse(json_data['preview']['body'])['page_nr']).to eq 2 + it 'returns a JSON response of the data from the content partner based on the data from the API' do + get pipeline_harvest_definition_extraction_definition_request_path(pipeline, harvest_definition, + extraction_definition, request_two) + + expect(response).to have_http_status :ok + + json_data = response.parsed_body + + expected_keys = %w[http_method base_url url format preview] + + expected_keys.each do |key| + expect(json_data).to have_key(key) + end + + content_source_response = JSON.parse(json_data['preview']['body']) + + expect(content_source_response).to have_key('count') + expect(content_source_response).to have_key('items') + end end end end diff --git a/spec/sidekiq/extraction_worker_spec.rb b/spec/sidekiq/extraction_worker_spec.rb index ea3302ef..b22a46f3 100644 --- a/spec/sidekiq/extraction_worker_spec.rb +++ b/spec/sidekiq/extraction_worker_spec.rb @@ -30,8 +30,7 @@ context 'when the extraction is for an enrichment' do let(:destination) { create(:destination) } let(:extraction_definition) do - create(:extraction_definition, kind: 'enrichment', destination:, source_id: 'test', - enrichment_url: 'http://www.google.co.nz') + create(:extraction_definition, kind: 'enrichment', destination:, source_id: 'test') end let(:enrichment_extraction_job) { create(:extraction_job, extraction_definition:, status: 'queued') } diff --git a/spec/supplejack/extraction/enrichment_execution_spec.rb b/spec/supplejack/extraction/enrichment_execution_spec.rb index 6e21f51d..b5fc5dd6 100644 --- a/spec/supplejack/extraction/enrichment_execution_spec.rb +++ b/spec/supplejack/extraction/enrichment_execution_spec.rb @@ -7,6 +7,11 @@ let(:extraction_definition) { create(:extraction_definition, :enrichment, destination:, throttle: 0) } let(:sample_job) { create(:extraction_job, extraction_definition:, kind: 'sample') } let(:full_job) { create(:extraction_job, extraction_definition:, kind: 'full') } + + let!(:request_one) { create(:request, extraction_definition:) } + let!(:request_two) { create(:request, extraction_definition:) } + + let!(:parameter) { create(:parameter, content: "response['dc_identifier'].first", kind: 'slug', request: request_two, content_type: 'dynamic') } describe '#call' do before do diff --git a/spec/supplejack/extraction/enrichment_extraction_spec.rb b/spec/supplejack/extraction/enrichment_extraction_spec.rb index ed5515dc..1e2e6bdb 100644 --- a/spec/supplejack/extraction/enrichment_extraction_spec.rb +++ b/spec/supplejack/extraction/enrichment_extraction_spec.rb @@ -3,16 +3,23 @@ require 'rails_helper' RSpec.describe Extraction::EnrichmentExtraction do - subject { described_class.new(ed, records.first, 1, extraction_job.extraction_folder) } + subject { described_class.new(request_two, record, 1, extraction_job.extraction_folder) } let(:extraction_job) { create(:extraction_job) } let(:destination) { create(:destination) } let(:ed) { create(:extraction_definition, :enrichment, destination:, extraction_jobs: [extraction_job]) } - let(:re) { Extraction::RecordExtraction.new(ed, 1).extract } + let(:re) { Extraction::RecordExtraction.new(request_one, 1).extract } let(:records) { JSON.parse(re.body)['records'] } + let(:record) { Extraction::ApiRecord.new(records.first) } + + let!(:request_one) { create(:request, extraction_definition: ed) } + let!(:request_two) { create(:request, extraction_definition: ed) } + + let!(:parameter) { create(:parameter, content: "response['dc_identifier'].first", kind: 'slug', request: request_two, content_type: 'dynamic') } before do stub_figshare_enrichment_page1(destination) + stub_figshare_enrichment_page2(destination) end describe '#extract' do @@ -33,7 +40,7 @@ context 'when there is no extraction_folder' do it 'returns an extracted document from a content source' do - doc = described_class.new(ed, records.first, 1) + doc = described_class.new(request_two, record, 1) expect { doc.save }.to raise_error(ArgumentError, 'extraction_folder was not provided in #new') end end @@ -60,13 +67,9 @@ end it 'returns false if the provided enrichment url returns nothing from the record' do - ed = create( - :extraction_definition, :enrichment, - destination:, - extraction_jobs: [extraction_job], - enrichment_url: '"http://www.google.co.nz/#{record["bla"]}"' - ) - expect(described_class.new(ed, records.first, 1, extraction_job.extraction_folder).valid?).to be false + record = Extraction::ApiRecord.new({ 'hello' => 'goodbye'} ) + + expect(described_class.new(request_two, record, 1, extraction_job.extraction_folder).valid?).to be false end end end diff --git a/spec/supplejack/extraction/record_extraction_spec.rb b/spec/supplejack/extraction/record_extraction_spec.rb index 668e221b..a037bb77 100644 --- a/spec/supplejack/extraction/record_extraction_spec.rb +++ b/spec/supplejack/extraction/record_extraction_spec.rb @@ -6,6 +6,9 @@ let(:destination) { create(:destination) } let(:extraction_definition) { create(:extraction_definition, :enrichment, destination:) } + let!(:request) { create(:request, extraction_definition:) } + + describe '#extract' do context 'when the enrichment is not scheduled after a harvest' do before do @@ -25,7 +28,7 @@ ).to_return(fake_response('test_api_records_1')) end - let(:subject) { described_class.new(extraction_definition, 1) } + let(:subject) { described_class.new(request, 1) } it 'returns an extracted document from a Supplejack API' do expect(subject.extract).to be_a(Extraction::Document) @@ -39,7 +42,7 @@ let(:harvest_job) do create(:harvest_job, :completed, harvest_definition:, pipeline_job:, target_job_id: 'harvest-job-1') end - let(:subject) { described_class.new(extraction_definition, 1, harvest_job) } + let(:subject) { described_class.new(request, 1, harvest_job) } before do stub_request(:get, "#{destination.url}/harvester/records")