Skip to content

Commit

Permalink
Merge pull request #373 from freelawproject/feat-adds-logic-to-handle…
Browse files Browse the repository at this point in the history
…-download-confirmation-page

feat(acms): Adds helper method to handle the Download Confirmation Page
  • Loading branch information
mlissner authored May 14, 2024
2 parents 3a2a781 + 1d55c5f commit 40a6be1
Show file tree
Hide file tree
Showing 11 changed files with 512 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The following changes are not yet released, but are code complete:
Features:
- Introduces a new upload type for Dockets from ACMS and enhances code maintainability by refactoring common upload logic into reusable functions.([#368](https://github.com/freelawproject/recap-chrome/pull/371))
- Docket reports from ACMS are now uploaded to CourtListener.([#372](https://github.com/freelawproject/recap-chrome/pull/372))
- Adds logic to upload ACMS PDF documents to CourtListener. ([#373](https://github.com/freelawproject/recap-chrome/pull/373))

Changes:
- None yet
Expand Down
29 changes: 29 additions & 0 deletions spec/AppellateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,35 @@ describe('The Appellate module', function () {
});
});

describe('parseReceiptPageTitle', function () {
it('parses title from download page', function () {
let titleData = APPELLATE.parseReceiptPageTitle(
'Document: PDF Document (Case: 22-11187, Document: 49)'
);
expect(titleData.docket_number).toBe('22-11187');
expect(titleData.doc_number).toBe('49');
expect(titleData.att_number).toBe(0);
});

it('parses title with attachment number from download page', function () {
let titleData = APPELLATE.parseReceiptPageTitle(
'Document: PDF Document (Case: 22-11187, Document: 43-1)'
);
expect(titleData.docket_number).toBe('22-11187');
expect(titleData.doc_number).toBe('43');
expect(titleData.att_number).toBe('1');
});

it('parses title from ACMS download page', function () {
let titleData = APPELLATE.parseReceiptPageTitle(
'Document: PDF Document (Case: 23-2487, Document: 3.1)'
);
expect(titleData.docket_number).toBe('23-2487');
expect(titleData.doc_number).toBe('3');
expect(titleData.att_number).toBe('1');
});
});

describe('getServletFromInputs', function () {
describe('for pages with matching format', function () {
beforeEach(function () {
Expand Down
18 changes: 14 additions & 4 deletions spec/ContentDelegateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1102,10 +1102,20 @@ describe('The ContentDelegate class', function () {
},
},
};
spyOn(cd.recap, 'uploadDocument').and.callFake((court, caseId, docId, docNumber, attachNumber, callback) => {
callback.tab = { id: 1234 };
callback(true);
});
spyOn(cd.recap, 'uploadDocument').and.callFake(
(
court,
caseId,
docId,
docNumber,
attachNumber,
acmsDocumentGuid,
callback
) => {
callback.tab = { id: 1234 };
callback(true);
}
);
document.getElementById = jasmine.createSpy('getElementById').and.callFake((id) => {
return document.querySelectorAll(`#${id}`)[0];
});
Expand Down
21 changes: 17 additions & 4 deletions spec/RecapSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,15 @@ describe('The Recap export module', function () {
existingFormData = window.FormData;
window.FormData = FormDataFake;
spyOn(recap, 'uploadDocument').and.callFake(
(court, case_id, doc_id, doc_number, attach_number, cb) => {
(
court,
case_id,
doc_id,
doc_number,
attach_number,
acmsDocumentGuid,
cb
) => {
cb(true);
}
);
Expand All @@ -245,8 +253,14 @@ describe('The Recap export module', function () {
expected.append('filepath_local', blob);

await recap.uploadDocument(
court, pacer_case_id, pacer_doc_id, docnum, attachnum,
callback);
court,
pacer_case_id,
pacer_doc_id,
docnum,
attachnum,
null,
callback
);
expect(callback).toHaveBeenCalledWith(true);
});
});
Expand Down Expand Up @@ -288,7 +302,6 @@ describe('The Recap export module', function () {

describe('uploadIQueryPage', function (){
let existingFormData;

beforeEach(async () => {
existingFormData = window.FormData;
window.FormData = FormDataFake;
Expand Down
124 changes: 124 additions & 0 deletions src/appellate/acms_api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
function Acms() {
// Asynchronously checks the progress of the PDF link generation using a
// provided URL and authorization token.
// This function fetches data from the specified URL using a GET request with
// an Authorization header containing a Bearer token. It then parses the JSON
// response and throws an error if the request fails (status code is not 200).
// Otherwise, it returns the parsed JSON data.
//
// returns A promise that resolves to the parsed JSON data
// on success, or rejects with an error if the request fails.
async function checkProgress(url, token) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});

const data = await response.json();

if (!response.ok)
throw new Error(`Error attempting to fetch with checkProgress: ${data}`);

return data;
}

// Asynchronously submits data to a server using a POST request.
// This function sends a POST request to the specified URL with an
// authorization token and the provided data.
// The data is expected to be a JavaScript object and will be converted
// to format before sending and The Authorization header includes a Bearer
// token for authentication. The function parses the JSON response and
// throws an error if the request fails (status code is not 200 OK).
// Otherwise, it returns the parsed JSON data.
//
// Returns A promise that resolves to the parsed JSON data on success, or
// rejects with an error if the request fails.
async function postData(url, token, body = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

const data = await response.json();

if (!response.ok)
throw new Error(`Error attempting to fetch with postData: ${data}`);

return data;
}

// This function simulates a while loop using Promises. It takes three
// arguments:
// - `input`: The initial value for the loop.
// - `condition`: A function that evaluates to true as long as the loop
// should continue.
// - `action`: A function that returns a Promise and performs the loop's
// logic for each iteration.
//
// It returns a Promise that resolves with the final `input` value when the
// `condition` function becomes false.
//
// It works by defining a recursive helper function `whilst`. This function
// checks the `condition` on the current `input`.
// - If `condition` is true, it calls the `action` function with `input`.
// The result (a Promise) is then chained with another call to `whilst`,
// effectively continuing the loop.
// - If `condition` is false, it resolves the returned Promise immediately
// with the current `input` value, signifying the end of the loop.
const promiseWhile = (input, condition, action) => {
const whilst = (input) =>
condition(input) ? action(input).then(whilst) : Promise.resolve(input);
return whilst(input);
};

return {
// Use the ACMS API to retrieve the file GUID and compute the document URL
getDocumentURL: async function (apiUrl, token, mergePdfFilesRequest, cb) {
const url = `${apiUrl}/MergePDFFiles`;
const data = await postData(url, token, mergePdfFilesRequest);

const statusQueryUrl = data && data.statusQueryGetUri;

// Prepare elements to be used within the promiseWhile loop.
// Helper function to check if the job is completed based on a
// given status
const isCompleted = (status) => /Completed/i.test(status);
// Helper function to determine if we should continue checking
const condition = (runtimeStatus) => !isCompleted(runtimeStatus);
// Initial runtime status (empty string)
const initialRuntimeStatus = '';

// Function to perform a single check on the job progress
const action = (runtimeStatus) =>
new Promise((resolve, reject) => {
checkProgress(statusQueryUrl, token)
.then((response) => {
// Extract the new runtime status from the response (if it exists)
const newRuntimeStatus = response && response.runtimeStatus;

if (!newRuntimeStatus) reject('No runtime status was returned.');

// Update the output variable if the job is completed
if (isCompleted(newRuntimeStatus))
output = response && response.output;

resolve(newRuntimeStatus);
})
.catch((error) => {
console.log(error);
});
});

// Use promiseWhile to keep checking the status until the job is completed
let fileGuid = await promiseWhile(
initialRuntimeStatus,
condition,
action
).then((status) => output);
cb(`${apiUrl}/GetMergedFile?fileGuid=${fileGuid}`);
},
};
}
Loading

0 comments on commit 40a6be1

Please sign in to comment.