Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pacer): Refine multi-document page handling logic #402

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2277d9f
feat(appellate): Refine multi-document page handling logic
ERosendo Sep 30, 2024
ea84011
feat(district): Refine multi-document page handling logic
ERosendo Sep 30, 2024
4d18676
feat(docs): Changelog Update
ERosendo Sep 30, 2024
c241760
feat(utils): Adds helper method to get pacer doc ids using exclude lists
ERosendo Sep 30, 2024
4a8db9b
feat(district): Adds helper function to check document availability
ERosendo Sep 30, 2024
3ba7123
feat(appellate): Tweaks the findDocLinksFromAnchors method
ERosendo Sep 30, 2024
16d580c
feat(utils): Introduces getDocToCasesMapping helper function
ERosendo Sep 30, 2024
affc549
feat(utils): Adds helper method to get docId using shortened version
ERosendo Sep 30, 2024
6ed14c5
feat(appellate): Adds helper function to check document availability
ERosendo Sep 30, 2024
1a1656a
feat(district): Adds logic to upload file from multi-doc page
ERosendo Sep 30, 2024
b322ae6
feat(util): Adds helper function to parse data from the receipt table
ERosendo Oct 1, 2024
43b6a8a
feat(appellate): Adds logic to upload file from multi-doc page
ERosendo Oct 1, 2024
dbb4b31
feat(pdf_upload): Add MIME type validation to ensure data integrity
ERosendo Oct 1, 2024
948ab40
Merge branch 'main' into 349-feat-identify-multidoc-pages-with-one-doc
ERosendo Dec 9, 2024
d1622c0
fix(acms): Tweaks logic to display banner for available documents
ERosendo Dec 11, 2024
864e4cd
feat(appellate): Introduces document-to-attachment-number mapping
ERosendo Dec 11, 2024
7219201
feat(appellate): Refines logic to handle missing attachment numbers
ERosendo Dec 11, 2024
7d775ce
refactor(appellate): Extract logic to create button for filers
ERosendo Dec 11, 2024
780864a
feat(pacer): Adds helper to check single docs in combined PDF pages
ERosendo Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
The following changes are not yet released, but are code complete:

Features:
- None yet
- Refines logic to accurately identify multi-document pages containing a single file ([349](https://github.com/freelawproject/recap/issues/349), [402](https://github.com/freelawproject/recap-chrome/pull/402))

Changes:
- None yet
Expand Down
145 changes: 114 additions & 31 deletions src/appellate/appellate.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,15 @@ AppellateDelegate.prototype.handleAcmsDownloadPage = async function () {
console.info(
'RECAP: Got results from API. Processing results to insert banner'
);
// To accurately identify ACMS documents, we should prioritize the
// `ACMS document details ID` stored in browser sessionStorage over
// the docket entry ID. This is because ACMS often uses the same URL
// for different attachments, making it ambiguous for identification
// purposes.
let acms_doc_id =
downloadData.docketEntryDocuments[0].docketDocumentDetailsId;
let result = recapLinks.results.filter(
(obj) => obj.pacer_doc_id == this.docId,
(obj) => obj.acms_document_guid == acms_doc_id,
this
)[0];
if (!result) return;
Expand Down Expand Up @@ -839,12 +846,13 @@ AppellateDelegate.prototype.attachRecapLinksToEligibleDocs = async function () {
}

// filter the links for the documents available on the page
let { links, docsToCases } = APPELLATE.findDocLinksFromAnchors(
this.links,
this.tabId,
this.queryParameters,
this.docketNumber
);
let { links, docsToCases, docsToAttachmentNumbers } =
APPELLATE.findDocLinksFromAnchors(
this.links,
this.tabId,
this.queryParameters,
this.docketNumber
);

this.pacer_case_id = this.pacer_case_id
? this.pacer_case_id
Expand All @@ -864,6 +872,7 @@ AppellateDelegate.prototype.attachRecapLinksToEligibleDocs = async function () {
caseId: this.pacer_case_id,
docketNumber: this.docketNumber,
docsToCases: docsToCases,
docsToAttachmentNumbers: docsToAttachmentNumbers,
},
});

Expand Down Expand Up @@ -1019,14 +1028,7 @@ AppellateDelegate.prototype.handleAttachmentPage = async function () {
history.replaceState({ uploaded: true }, '');
};

AppellateDelegate.prototype.handleCombinedPdfPageView = async function () {
let warning = combinedPdfWarning();
document.body.appendChild(warning);
};

// If this page offers a single document, intercept navigation to the document
// view page.
AppellateDelegate.prototype.handleSingleDocumentPageView = async function () {
AppellateDelegate.prototype.overrideDefaultForm = async function () {
if (PACER.hasFilingCookie(document.cookie)) {
let button = createRecapButtonForFilers(
'Accept Charges and RECAP Document'
Expand All @@ -1050,6 +1052,60 @@ AppellateDelegate.prototype.handleSingleDocumentPageView = async function () {
} else {
await overwriteFormSubmitMethod();
}
};

AppellateDelegate.prototype.handleCombinedPdfPageView = async function () {
// Find all `center` divs, which typically wrap receipt tables in lower
// courts. However, in appellate courts, these divs can also wrap the entire
// page content. To ensure an accurate count, we filter out nodes with more
// than 3 child elements, as a full page container would likely have more
// content.
let transactionReceiptTables = Array.from(
document.querySelectorAll('center')
).filter((row) => row.childElementCount <= 3);
if (transactionReceiptTables.length === 0) return;

// In appellate courts, we don't rely on an exclusion list like lower courts.
// Instead, we extract document IDs from the URL parameters (`dls` attribute).
// These IDs represent the files included on the current page.
const urlParams = new URLSearchParams(window.location.search);
let includeList = urlParams.get('dls') ? urlParams.get('dls').split(',') : [];

// Count the number of receipt tables (from `transactionReceiptTables`) and
// documents listed in the URL (`includeList`). If either count is greater
// than 1, it indicates multiple documents are present. In this case, display
// a warning message.
if (
transactionReceiptTables.length > 1 ||
(includeList && includeList.length > 1)
) {
const warning = combinedPdfWarning();
document.body.appendChild(warning);
return;
}

await this.overrideDefaultForm();

// When we receive the message from the above submit method, submit the form
// via XHR so we can get the document before the browser does.
window.addEventListener(
'message',
this.onDocumentViewSubmit.bind(this),
false
);

this.docId = await checkSingleDocInCombinedPDFPage(
this.tabId,
this.court,
this.docId,
true
);
};

// If this page offers a single document, intercept navigation to the document
// view page.
AppellateDelegate.prototype.handleSingleDocumentPageView = async function () {
await this.overrideDefaultForm();

this.pacer_case_id = await APPELLATE.getCaseId(
this.tabId,
Expand Down Expand Up @@ -1112,38 +1168,65 @@ AppellateDelegate.prototype.onDocumentViewSubmit = async function (event) {
let form = document.getElementById(event.data.id);

let title = document.querySelectorAll('strong')[1].innerHTML;
let dataFromTitle = APPELLATE.parseReceiptPageTitle(title);
let pdfData = APPELLATE.parseReceiptPageTitle(title);

if (!dataFromTitle.att_number && this.queryParameters.get('recapAttNum')) {
dataFromTitle.att_number = this.queryParameters.get('recapAttNum');
// For multi-document pages, the title alone doesn't provide sufficient
// information. Therefore, we need to extract additional data from the
// receipt table to accurately identify the PDF.
// Attempt to parse the necessary data from the receipt table.
if (!pdfData) pdfData = APPELLATE.parseDataFromReceiptTable();

// If we still don't have enough data after parsing the receipt table,
// submit the form without retrieving the file.
if (!pdfData) {
form.submit();
return;
}

if (dataFromTitle.doc_number.length > 9) {
if (!pdfData.att_number) {
if (this.queryParameters.get('recapAttNum')) {
pdfData.att_number = this.queryParameters.get('recapAttNum');
} else {
pdfData.att_number = await getAttachmentNumberFromPacerDocId(
this.tabId,
this.docId
);
}
}

if (pdfData.doc_number.length > 9) {
// If the number is really big, it's probably a court that uses
// pacer_doc_id instead of regular docket entry numbering.
dataFromTitle.doc_number = PACER.cleanPacerDocId(dataFromTitle.doc_number);
pdfData.doc_number = PACER.cleanPacerDocId(pdfData.doc_number);
}

if (!dataFromTitle) {
form.submit();
return;
}
$('body').css('cursor', 'wait');
let query_string = new URLSearchParams(new FormData(form)).toString();
const resp = await window.fetch(form.action, {
method: form.method,
headers: {
// The form method in multi-document pages is a GET request, while
// single-document pages use a POST request. By checking the method here, we
// can reuse this code to retrieve the PDF file and display it appropriately
// for both page types.
let queryString = new URLSearchParams(new FormData(form));
let url = new URL(form.action);;
let method = form.method.toUpperCase();
let options = {};
if (method == 'GET') {
// If the method is GET, append query parameters to the URL
queryString.forEach((value, key) => url.searchParams.append(key, value));
} else {
options['method'] = method;
options['headers'] = {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: query_string
});
options['body'] = queryString.toString();
}
const resp = await window.fetch(url, options);
let helperMethod = handleDocFormResponse.bind(this);
helperMethod(
resp.headers.get('Content-Type'),
await resp.blob(),
null,
previousPageHtml,
dataFromTitle
pdfData
);
};

Expand Down
36 changes: 30 additions & 6 deletions src/appellate/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,20 +235,23 @@ let APPELLATE = {
findDocLinksFromAnchors: function (nodeList, tabId, queryParameters, docketNumber) {
let links = [];
let docsToCases = {};
let docsToAttachmentNumbers = {};
Array.from(nodeList).map((a) => {
if (!PACER.isDoc1Url(a.href)) return;

let docNum = PACER.getDocNumberFromAnchor(a) || queryParameters.get('recapDocNum');
let docNum =
PACER.getDocNumberFromAnchor(a) || queryParameters.get('recapDocNum');
let doDoc = PACER.parseDoDocPostURL(a.getAttribute('onclick'));
if (doDoc && doDoc.doc_id && doDoc.case_id) {
docsToCases[doDoc.doc_id] = doDoc.case_id;
let pacerCaseId =
(doDoc && doDoc.case_id) || queryParameters.get('caseId');
if (doDoc && doDoc.doc_id && pacerCaseId) {
docsToCases[doDoc.doc_id] = pacerCaseId;
}

a.removeAttribute('onclick');
a.setAttribute('target', '_self');

let url = new URL(a.href);
let pacerCaseId = (doDoc && doDoc.case_id) || queryParameters.get('caseId');
url.searchParams.set('caseId', pacerCaseId);

if (docNum) {
Expand Down Expand Up @@ -288,11 +291,14 @@ let APPELLATE = {
clonedNode.dataset.pacerCaseId = pacerCaseId;
clonedNode.dataset.pacerTabId = tabId;
clonedNode.dataset.documentNumber = docNum ? docNum : docId;
if (attNumber) clonedNode.dataset.attachmentNumber = attNumber;
if (attNumber) {
clonedNode.dataset.attachmentNumber = attNumber;
docsToAttachmentNumbers[docId] = attNumber;
}

links.push(docId);
});
return { links, docsToCases };
return { links, docsToCases , docsToAttachmentNumbers};
},

// get the docId from the servlet parameter of the attachment page or the single doc page
Expand Down Expand Up @@ -370,6 +376,24 @@ let APPELLATE = {
return r;
},

// returns data from the table of the Receipt Page as an object
parseDataFromReceiptTable: () => {
// this method uses regex expressions to match that information from the
// receipt table and returns an object with the following attributes:
// - docket_number
// - doc_number
// - att_number (if applicable).
let search_string = $('td:contains(Case)').text();
let regex =
/Case: (?<docket_number>[^']*), Document: (?<doc_number>\d+)(-(?<att_number>\d+))?/;
let matches = regex.exec(search_string);
// If no matches were found, return null.
if (!matches) return null;
// If an attachment number was not found, set it to 0.
if (!matches.groups.att_number) matches.groups.att_number = 0;
return matches.groups;
},

// Returns an object with the court Id and docket number core extracted from a link to district court
getDatafromDistrictLinkUrl: (url) => {
// Converts links to district courts like:
Expand Down
Loading
Loading