Skip to content

Add issue table view#691

Merged
GeorgeGoodall-GovUk merged 56 commits into
mainfrom
423-add-issues-table-component-to-issues-details-page-attempt-2
Dec 2, 2024
Merged

Add issue table view#691
GeorgeGoodall-GovUk merged 56 commits into
mainfrom
423-add-issues-table-component-to-issues-details-page-attempt-2

Conversation

@GeorgeGoodall-GovUk
Copy link
Copy Markdown
Contributor

@GeorgeGoodall-GovUk GeorgeGoodall-GovUk commented Nov 25, 2024

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Adds the issue table page
moved some code from issue details and dataview to common and refactored for reuse

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

Preview Link: https://submit-pr-691.herokuapp.com/organisations/local-authority:WRT/brownfield-land/invalid%20URI/SiteplanURL/entry

image

Added/updated tests?

  • Yes

QA sign off

  • Code has been checked and approved
  • Design has been checked and approved
  • Product and business logic has been checked and proved

[optional] Are there any post-deployment tasks we need to perform?

  • We need to update other areas of the site to get issues using the new entity issue link.
    • for now we have agreed to go ahead with this deployment, but pick up this other work as a priority

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced middleware for managing issue tables, including validation and data preparation.
    • Enhanced pagination handling with new functions for calculating data ranges and setting base paths.
    • Updated templates for displaying error summaries and issue details, improving user experience.
    • Added new schemas for structured data representation in requests and responses.
    • Implemented new middleware functions for streamlined processing of dataset specifications and entity issues.
    • Added a new property for pagination settings in the configuration.
  • Bug Fixes

    • Improved error handling in templates for issue details and summaries.
    • Restructured pagination logic to ensure correct display based on available data.
  • Documentation

    • Updated schemas for better data structure representation in requests and responses.
  • Tests

    • Expanded test coverage for middleware functions and templates, ensuring robust validation of new features and bug fixes.

…issues-table-component-to-issues-details-page-part-2
@GeorgeGoodall-GovUk GeorgeGoodall-GovUk linked an issue Nov 25, 2024 that may be closed by this pull request
33 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 25, 2024

Walkthrough

The changes in this pull request involve significant enhancements to middleware and routing related to issue management within the application. A new middleware for handling issue tables has been introduced, alongside modifications to existing middleware to improve pagination and data handling. The routing for issue details has been updated to include new paths, and several new schemas have been defined for better data validation. Additionally, the HTML templates have been adjusted to reflect these changes, ensuring a more structured presentation of issue data.

Changes

File Change Summary
src/controllers/OrganisationsController.js Added getIssueTableMiddleware to the organisationsController.
src/middleware/common.middleware.js Introduced new functions for pagination and issue handling; replaced existing pagination logic.
src/middleware/dataview.middleware.js Removed several functions related to pagination; added getDataRange for dynamic pagination setup.
src/middleware/issueDetails.middleware.js Added setBaseSubpath and getDataRange; updated pagination handling.
src/middleware/issueTable.middleware.js Created new middleware for managing issue tables with validation and data preparation functions.
src/routes/organisations.js Updated and added routes for issue details and issue tables, including new URL segments.
src/routes/schemas.js Added new schemas for data range and error summaries; updated existing schemas for consistency.
src/views/organisations/dataview.html Modified HTML to reposition the row range display based on pagination.
src/views/organisations/issueDetails.html Updated error summary handling to use a structured object.
src/views/organisations/issueTable.html New file created for rendering issue tables with pagination and error summaries.
test/unit/middleware/common.middleware.test.js Expanded tests for new middleware functions and pagination logic.
test/unit/middleware/dataview.middleware.test.js Updated tests to reflect changes in pagination handling.
test/unit/middleware/issueDetails.middleware.test.js Adjusted tests for updated template parameters.
test/unit/middleware/issueTable.middleware.test.js New test suite for issue table middleware functionalities.
test/unit/views/organisations/issueDetailsPage.test.js Refactored tests to align with new error summary structure.

Assessment against linked issues

Objective Addressed Explanation
As a data provider, when I'm on the dataset issues page, I see a table that shows an aggregated view.
The table contains a link to each individual entity that takes me to the entity-level view.
When I'm on the entity-level view, I see a link to take me back to the table view.
Pagination should be included in the table view.
Add test code for new template issueTable.html. Test code for the new template is planned for a future ticket.

Possibly related PRs

Suggested labels

enhancement, tech debt

Suggested reviewers

  • DilwoarH
  • rosado

🐰 In the meadow, where data flows,
A table of issues, where clarity grows.
With links to the entities, all in a row,
Pagination added, to help us know.
So hop along, let’s see what we find,
In this garden of data, so well aligned! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 25, 2024

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 70.17% 5088 / 7250
🔵 Statements 70.17% 5088 / 7250
🔵 Functions 66.88% 202 / 302
🔵 Branches 83.61% 648 / 775
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
config/util.js 93.79% 80% 100% 93.79% 121-128
src/controllers/OrganisationsController.js 0% 0% 0% 0% 1-21
src/middleware/common.middleware.js 89.56% 95.55% 41.17% 89.56% 19-28, 32, 53-54, 66-68, 79, 95-101, 190-195, 296, 305-307, 330-332, 360-368, 456-460, 465-468
src/middleware/dataview.middleware.js 97.22% 90.9% 42.85% 97.22% 30-33
src/middleware/issueDetails.middleware.js 77.81% 70% 35.71% 77.81% 51-63, 76-99, 173-176, 179-193, 227-233
src/middleware/issueTable.middleware.js 99.05% 96.55% 88.88% 99.05% 62-63
src/routes/organisations.js 0% 0% 0% 0% 1-18
src/routes/schemas.js 100% 100% 100% 100%
Generated in workflow #388 for commit 9952399 by the Vitest Coverage Report Action

…issues-table-component-to-issues-details-page-attempt-2
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
test/unit/middleware/common.middleware.test.js (2)

12-94: Consider adding edge case tests for pagination validation

The test suite provides good coverage but would benefit from additional edge cases:

  • Maximum safe integer value for page number
  • Floating-point page numbers
  • String numbers (e.g., "1", "2")

786-976: Add timeout handling tests for datasette queries

Consider adding test cases for:

  • Query timeout scenarios
  • Network latency simulation
  • Rate limiting responses

Example implementation:

it('handles query timeout', async () => {
  datasette.runQuery.mockImplementation(() => new Promise(resolve => 
    setTimeout(resolve, 5000)
  ));
  // Test implementation
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 13eed87 and 3e3d19b.

📒 Files selected for processing (1)
  • test/unit/middleware/common.middleware.test.js (2 hunks)
🔇 Additional comments (3)
test/unit/middleware/common.middleware.test.js (3)

127-142: Update error handling in pagination tests

The test expects direct error throwing, but middleware should pass errors to the next function instead.


464-469: Update error handling in specification tests

The test expects direct error throwing, but middleware should pass errors to the next function instead.


978-1065: Well-structured test suite with comprehensive coverage

The test cases effectively cover:

  • Basic functionality
  • Edge cases
  • Error scenarios
  • Undefined/empty inputs

Comment thread test/unit/middleware/common.middleware.test.js
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3e3d19b and e614650.

📒 Files selected for processing (1)
  • src/middleware/issueDetails.middleware.js (5 hunks)
🔇 Additional comments (3)
src/middleware/issueDetails.middleware.js (3)

84-84: Proper null check implemented

The null check for issueEntitiesCount has been correctly implemented as previously suggested.


172-176: LGTM! URL-safe path construction

The function correctly encodes URL parameters to prevent injection vulnerabilities.


288-291: LGTM! Logical middleware ordering

The new middleware functions are added in the correct order, ensuring each has access to the data it needs from previous steps.

Comment thread src/middleware/issueDetails.middleware.js
Comment thread src/middleware/issueDetails.middleware.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (8)
src/middleware/issueTable.middleware.js (5)

1-7: Consider enhancing documentation with JSDoc format

The current documentation clearly outlines the middleware's purpose, but converting it to JSDoc format would provide better IDE support and documentation generation capabilities.

-/*
-  this middleware should get all entities for the organisation, with all of the most recent issues
-  then filter out any of the issues that have been fixed
-  the construct the table params
-  then construct the template params
-  then render the template
-*/
+/**
+ * Middleware for handling issue table views.
+ * 
+ * Flow:
+ * 1. Fetches all entities for the organisation with most recent issues
+ * 2. Filters out fixed issues
+ * 3. Constructs table parameters
+ * 4. Prepares template parameters
+ * 5. Renders the template
+ * 
+ * @middleware
+ */

28-41: Consider extracting page length as a configurable constant

The page length (50) is currently hardcoded. Consider moving this to a configuration file for better maintainability and flexibility.

+// At the top of the file
+const DEFAULT_PAGE_LENGTH = 50
+
 export const getDataRange = (req, res, next) => {
   const { issues } = req
   const { pageNumber } = req.parsedParams
-  const pageLength = 50
+  const pageLength = DEFAULT_PAGE_LENGTH

49-91: Consider optimising data processing for better performance

The current implementation uses nested mapping and filtering operations which could be optimised for better performance with larger datasets.

-  const allRows = entities.map((entity, index) => ({
-    columns: Object.fromEntries(uniqueDatasetFields.map((field) => {
+  const errorsByEntityAndField = new Map(
+    issues.map(issue => [`${issue.entity}-${issue.field}`, issue.issue_type])
+  )
+  
+  const allRows = entities.map((entity, index) => ({
+    columns: Object.fromEntries(uniqueDatasetFields.map((field) => {
-      const errorMessage = issues.find(issue => issue.entity === entity.entity && (issue.field === field || issue.replacement_field === field))?.issue_type
+      const errorMessage = errorsByEntityAndField.get(`${entity.entity}-${field}`)

157-158: Remove commented-out code

These commented parameters appear to be unused. If they're no longer needed, they should be removed to maintain code cleanliness.

-    // issueEntitiesCount,
-    // pageNumber

165-178: Consider adding TypeScript-style type definitions

Adding type definitions would improve code maintainability and prevent potential errors when modifying redirect conditions.

+/** @typedef {{ type: string, field: string }} RedirectGroup */

+/** @type {RedirectGroup[]} */
 const redirectIssueGroups = [
   {
     type: 'missing value',
     field: 'reference'
   },
   // ...
 ]
src/middleware/common.middleware.js (1)

Line range hint 143-177: Replace unsafe isNaN and improve error handling

  1. Replace global isNaN with Number.isNaN
  2. Consider adding a more descriptive error message
-  if (isNaN(pageNumber) || pageNumber < 1) {
+  if (Number.isNaN(pageNumber) || pageNumber < 1) {
-    const error = new Error('Invalid page number')
+    const error = new Error(`Invalid page number: ${pageNumber}`)
     return next(error)
   }
🧰 Tools
🪛 Biome (1.9.4)

[error] 122-122: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 143-143: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

test/unit/middleware/common.middleware.test.js (2)

785-867: Add edge cases for date handling in FilterOutIssuesToMostRecent tests

While the test coverage for date handling is good, consider adding these edge cases:

  • Dates in different timezones
  • Future dates
  • Dates near the Unix epoch
  • ISO 8601 date strings with different formats

871-883: Simplify mock implementation for better maintainability

The current mock implementation using array iteration could be simplified using a Map for better performance and readability.

Consider this alternative implementation:

-  const mockDatasetteQuery = (moreRecentEntityFieldsFacts) => {
-    datasette.runQuery.mockImplementation((query, dataset) => {
-      let formattedData = []
-      moreRecentEntityFieldsFacts.forEach(([entity, field, resource]) => {
-        if (query.includes(entity) && query.includes(field) && query.includes(resource)) {
-          formattedData = [{ entity, field, resource }]
-        }
-      })
-      return {
-        formattedData
-      }
-    })
-  }
+  const mockDatasetteQuery = (moreRecentEntityFieldsFacts) => {
+    const mockData = new Map(
+      moreRecentEntityFieldsFacts.map(([entity, field, resource]) => [
+        `${entity}-${field}-${resource}`,
+        { entity, field, resource }
+      ])
+    )
+    datasette.runQuery.mockImplementation((query) => {
+      const key = Array.from(mockData.keys()).find(k => query.includes(k))
+      return {
+        formattedData: key ? [mockData.get(key)] : []
+      }
+    })
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e614650 and 4de89ab.

📒 Files selected for processing (3)
  • src/middleware/common.middleware.js (6 hunks)
  • src/middleware/issueTable.middleware.js (1 hunks)
  • test/unit/middleware/common.middleware.test.js (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/middleware/common.middleware.js

[error] 122-122: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 143-143: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

🔇 Additional comments (12)
src/middleware/issueTable.middleware.js (5)

15-26: LGTM! Comprehensive validation schema with proper type transformations

The validation schema effectively handles all required query parameters with appropriate type checking and transformations.


43-47: LGTM! Proper URL encoding for path parameters

The implementation correctly encodes all path parameters, preventing potential XSS vulnerabilities.


194-212: LGTM! Well-structured middleware chain

The middleware chain is well-organised and follows a logical flow from validation through to rendering.


101-106: 🛠️ Refactor suggestion

Enhance error handling with custom error type

The current error handling could be more specific and informative.

+class IssueTableError extends Error {
+  constructor(message, code) {
+    super(message)
+    this.name = 'IssueTableError'
+    this.code = code
+  }
+}

 if (issues.length <= 0) {
   logger.warn(`issueTable was accessed from ${req.headers.referer} but there was no issues`)
-  const error = new Error('issue count must be larger than 0')
+  const error = new IssueTableError('No issues found to display', 'NO_ISSUES_FOUND')
   return next(error)
 }

Likely invalid or redundant comment.


102-102: Investigate potential bug in task list page

The comment suggests there might be a bug in how the task list page handles fixed issues.

src/middleware/common.middleware.js (6)

118-132: Replace unsafe isNaN with Number.isNaN

The use of global isNaN is unsafe as it attempts type coercion. Use Number.isNaN instead for more predictable type checking.

-  if (!dataRange || !dataRange.maxPageNumber || isNaN(dataRange.maxPageNumber)) {
+  if (!dataRange || !dataRange.maxPageNumber || Number.isNaN(dataRange.maxPageNumber)) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 122-122: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


188-198: Prevent SQL injection by parameterising query inputs

User-provided inputs req.params.lpa and req.params.dataset are directly interpolated into the SQL query. This could lead to SQL injection vulnerabilities.

export const fetchResources = fetchMany({
-  query: ({ req }) => `
-    select * from resource r
-    LEFT JOIN resource_organisation ro ON ro.resource = r.resource
-    LEFT JOIN resource_dataset rd ON rd.resource = r.resource
-    WHERE ro.organisation = '${req.params.lpa}'
-    AND rd.dataset = '${req.params.dataset}'
-    AND r.end_date = ''
-    ORDER BY start_date desc`,
+  query: ({ req }) => ({
+    query: `
+      SELECT * FROM resource r
+      LEFT JOIN resource_organisation ro ON ro.resource = r.resource
+      LEFT JOIN resource_dataset rd ON rd.resource = r.resource
+      WHERE ro.organisation = :organisation
+      AND rd.dataset = :dataset
+      AND r.end_date = ''
+      ORDER BY start_date DESC`,
+    params: {
+      organisation: req.params.lpa,
+      dataset: req.params.dataset
+    }
+  }),
  result: 'resources'
})

202-205: Prevent SQL injection by parameterising query inputs

The value req.dataset.collection is directly embedded into the SQL query without sanitisation.

export const fetchSpecification = fetchOne({
-  query: ({ req }) => `select * from specification WHERE specification = '${req.dataset.collection}'`,
+  query: ({ req }) => ({
+    query: 'SELECT * FROM specification WHERE specification = :specification',
+    params: {
+      specification: req.dataset.collection
+    }
+  }),
  result: 'specification'
})

358-372: Prevent SQL injection by parameterising query inputs

Parameters params.issue_type and params.issue_field are directly interpolated into the SQL query.

const fetchEntityIssuesForFieldAndType = fetchMany({
-  query: ({ req, params }) => {
-    const issueTypeFilter = params.issue_type ? `AND issue_type = '${params.issue_type}'` : ''
-    const issueFieldFilter = params.issue_field ? `AND field = '${params.issue_field}'` : ''
-
-    return `
-      SELECT e.entity, i.* FROM entity e
-      INNER JOIN issue i ON e.entity = i.entity
-      WHERE e.organisation_entity = ${req.orgInfo.entity}
-      ${issueTypeFilter}
-      ${issueFieldFilter}`
+  query: ({ req, params }) => {
+    const query = `
+      SELECT e.entity, i.* FROM entity e
+      INNER JOIN issue i ON e.entity = i.entity
+      WHERE e.organisation_entity = :organisationEntity
+      ${params.issue_type ? 'AND issue_type = :issueType' : ''}
+      ${params.issue_field ? 'AND field = :issueField' : ''}`
+
+    const queryParams = {
+      organisationEntity: req.orgInfo.entity
+    }
+    if (params.issue_type) queryParams.issueType = params.issue_type
+    if (params.issue_field) queryParams.issueField = params.issue_field
+
+    return { query, params: queryParams }
+  },
  dataset: FetchOptions.fromParams,
  result: 'issues'
})

421-470: Improve error handling and prevent SQL injection

The function has several areas for improvement:

  1. SQL injection vulnerability in the query
  2. Performance impact of multiple database queries
  3. Basic error handling in Promise chain
export const removeIssuesThatHaveBeenFixed = async (req, res, next) => {
  const { issues, resources } = req

  if (!resources || resources.length <= 0) {
    logger.warn('no resources provided for removeIssueThatHaveBeenFixed')
    return next()
  }

  const promises = issues
    .filter(issue => issue.resource !== resources[0].resource)
    .map((issue) => {
      const resourceIndex = resources.findIndex(resource => resource.resource === issue.resource)
      const newerResources = resourceIndex >= 0 ? resources.slice(0, resourceIndex) : resources

-      return datasette.runQuery(`
-        SELECT * FROM fact f
-        LEFT JOIN fact_resource fr ON f.fact = fr.fact
-        WHERE entity = ${issue.entity}
-        AND field = '${issue.field}'
-        AND fr.resource IN ('${newerResources.map(resource => resource.resource).join("','")}')
-        ORDER BY fr.start_date desc
-        LIMIT 1`,
-      issue.dataset
-      )
+      return datasette.runQuery({
+        query: `
+          SELECT * FROM fact f
+          LEFT JOIN fact_resource fr ON f.fact = fr.fact
+          WHERE entity = :entity
+          AND field = :field
+          AND fr.resource = ANY(:resources)
+          ORDER BY fr.start_date DESC
+          LIMIT 1`,
+        params: {
+          entity: issue.entity,
+          field: issue.field,
+          resources: newerResources.map(resource => resource.resource)
+        }
+      }, issue.dataset)
    })

  Promise.allSettled(promises)
    .then((results) => {
      results.forEach(result => {
        if (result.status === 'fulfilled') {
          if (result.value.formattedData.length > 0) {
            req.issues = req.issues.filter(issue => 
              issue.entity !== result.value.formattedData[0].entity || 
              issue.field !== result.value.formattedData[0].field
            )
          }
        } else {
-          logger.warn('request to datasette failed', {
+          logger.error('request to datasette failed', {
            error: result.reason,
            stack: result.reason.stack
          })
        }
      })
      return next()
    })
    .catch(error => {
-      logger.warn('Error in middleware, could not process promise array', {
+      logger.error('Error in middleware, could not process promise array', {
        error,
+       stack: error.stack
      })
      return next(error)
    })
}

294-300: ⚠️ Potential issue

Prevent SQL injection by parameterising query inputs

The value req.orgInfo.entity is directly interpolated into the SQL query.

export const fetchEntities = fetchMany({
-  query: ({ req }) => `
-    SELECT * FROM entity e
-    WHERE e.organisation_entity = ${req.orgInfo.entity}`,
+  query: ({ req }) => ({
+    query: `
+      SELECT * FROM entity e
+      WHERE e.organisation_entity = :organisationEntity`,
+    params: {
+      organisationEntity: req.orgInfo.entity
+    }
+  }),
  dataset: FetchOptions.fromParams,
  result: 'entities'
})

Likely invalid or redundant comment.

test/unit/middleware/common.middleware.test.js (1)

464-469: ⚠️ Potential issue

Update error handling pattern in middleware test

The test expects the middleware to throw an error directly, which doesn't follow the middleware error handling pattern. Middleware functions should pass errors to the next function.

Apply this diff to correct the test:

-  expect(() => getUniqueDatasetFieldsFromSpecification(req, res, next)).toThrowError('specification is required')
+  getUniqueDatasetFieldsFromSpecification(req, res, next)
+  expect(next).toHaveBeenCalledWith(new Error('specification is required'))

Likely invalid or redundant comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
config/default.yaml (1)

91-91: Consider restructuring the pagination configuration.

Whilst the default value of 50 is reasonable, consider the following improvements:

  1. Move this setting under a dedicated pagination section
  2. Add documentation explaining its purpose
  3. Include additional pagination-related settings that might be needed

Here's a suggested restructure:

-tablePageLength: 50
+# Configuration for pagination across the application
+pagination:
+  # Number of items to display per page in table views
+  tablePageLength: 50
+  # Maximum number of pages to show in pagination controls
+  maxPageLinks: 5
src/middleware/issueTable.middleware.js (4)

1-7: Enhance documentation with structured JSDoc comments

The current documentation provides a good overview but could be more structured. Consider using JSDoc format for better documentation clarity and IDE support.

-/*
-  this middleware should get all entities for the organisation, with all of the most recent issues
-  then filter out any of the issues that have been fixed
-  the construct the table params
-  then construct the template params
-  then render the template
-*/
+/**
+ * Middleware for handling issue table views.
+ * 
+ * Process flow:
+ * 1. Fetches all entities for the organisation with their most recent issues
+ * 2. Filters out fixed issues
+ * 3. Constructs table parameters
+ * 4. Prepares template parameters
+ * 5. Renders the template
+ * 
+ * @module issueTable.middleware
+ */

50-92: Consider extracting column preparation logic

The table preparation logic is quite complex. Consider extracting the column preparation logic into separate functions for better maintainability.

+const prepareReferenceColumn = (entity, field, index, lpa, dataset, issueType, issueField, errorMessage) => ({
+  html: `<a href='/organisations/${encodeURIComponent(lpa)}/${encodeURIComponent(dataset)}/${encodeURIComponent(issueType)}/${encodeURIComponent(issueField)}/entry/${index + 1}'>${entity[field]}</a>`,
+  error: errorMessage ? { message: errorMessage } : undefined
+})
+
+const prepareRegularColumn = (entity, field, errorMessage) => ({
+  value: entity[field],
+  error: errorMessage ? { message: errorMessage } : undefined
+})
+
 export const prepareTableParams = (req, res, next) => {
   const { entities, issues, uniqueDatasetFields, dataRange } = req
   const { lpa, dataset, issue_type: issueType, issue_field: issueField } = req.params

   const allRows = entities.map((entity, index) => ({
     columns: Object.fromEntries(uniqueDatasetFields.map((field) => {
       const errorMessage = issues.find(issue => issue.entity === entity.entity && (issue.field === field || issue.replacement_field === field))?.issue_type
-      if (field === 'reference') {
-        return [field, {
-          html: `<a href='/organisations/${encodeURIComponent(lpa)}/${encodeURIComponent(dataset)}/${encodeURIComponent(issueType)}/${encodeURIComponent(issueField)}/entry/${index + 1}'>${entity[field]}</a>`,
-          error: errorMessage
-            ? {
-                message: errorMessage
-              }
-            : undefined
-        }]
-      } else {
-        return [field, {
-          value: entity[field],
-          error: errorMessage
-            ? {
-                message: errorMessage
-              }
-            : undefined
-        }]
-      }
+      return [
+        field,
+        field === 'reference'
+          ? prepareReferenceColumn(entity, field, index, lpa, dataset, issueType, issueField, errorMessage)
+          : prepareRegularColumn(entity, field, errorMessage)
+      ]
     }))
   }))

103-103: Address the TODO comment in the warning message

The warning message indicates an issue with task list page functionality that needs to be addressed.

Would you like me to help create a GitHub issue to track the task list page issue?


146-162: Remove commented-out code

The template parameters object contains commented-out properties that should be removed if they're no longer needed.

   req.templateParams = {
     tableParams,
     organisation: orgInfo,
     dataset,
     errorSummary,
     issueType,
     pagination,
     dataRange
-    // issueEntitiesCount,
-    // pageNumber
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4de89ab and 4f21a53.

📒 Files selected for processing (2)
  • config/default.yaml (1 hunks)
  • src/middleware/issueTable.middleware.js (1 hunks)
🔇 Additional comments (8)
config/default.yaml (1)

91-91: Verify consistent usage of the pagination setting.

Let's ensure this configuration is being used consistently across the codebase and isn't duplicated elsewhere.

✅ Verification successful

Pagination configuration is used consistently across the codebase

The verification shows that:

  • The tablePageLength: 50 configuration is correctly used in issueTable.middleware.js
  • Other instances of pageLength: 50 in test files are consistent with this configuration
  • Different page lengths (1 and 10) are used intentionally for specific features:
    • pageLength: 1 for issue details
    • pageLength: 10 in common middleware tests
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any hardcoded pagination values or similar configuration

# Search for hardcoded page length values
echo "Checking for hardcoded pagination values..."
rg -n '(?i)(pageLength|itemsPerPage|perPage)\s*[=:]\s*\d+'

# Search for usage of tablePageLength
echo "Checking usage of tablePageLength configuration..."
rg -n '(?i)tablePageLength'

# Search for other pagination-related configuration
echo "Checking for other pagination configuration..."
rg -n '(?i)pagination.*config'

Length of output: 1623

src/middleware/issueTable.middleware.js (7)

16-28: LGTM! Robust query parameter validation

The validation schema is well-structured with proper type coercion and validation rules. Good use of the radix parameter in parseInt.


29-42: LGTM! Robust pagination calculation

The data range calculation is well-implemented with proper boundary handling and protection against edge cases.


44-48: LGTM! Secure path handling

Good use of encodeURIComponent for all path segments to prevent path traversal vulnerabilities.


164-187: LGTM! Clear and well-structured redirection logic

The redirection rules are well-organised and the implementation is clean.


189-193: LGTM! Clean template rendering setup

The template rendering is well-implemented using the renderTemplate helper.


195-213: LGTM! Well-structured middleware chain

The middleware chain is logically ordered and clearly composed, following a natural flow of operations.


102-107: 🛠️ Refactor suggestion

Enhance error handling with custom error class

The current error handling could be more specific and informative.

+class IssueTableError extends Error {
+  constructor(message, code) {
+    super(message);
+    this.name = 'IssueTableError';
+    this.code = code;
+  }
+}

 if (issues.length <= 0) {
   logger.warn(`issueTable was accessed from ${req.headers.referer} but there was no issues`)
-  const error = new Error('issue count must be larger than 0')
+  const error = new IssueTableError('No issues found to display in the table', 'NO_ISSUES_FOUND')
   return next(error)
 }

Likely invalid or redundant comment.

Comment thread config/default.yaml
Comment thread src/middleware/issueTable.middleware.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
src/middleware/issueTable.middleware.js (5)

1-7: Convert comment block to JSDoc format for better documentation

Consider converting the descriptive comment block to JSDoc format for better IDE integration and documentation generation.

-/*
-  this middleware should get all entities for the organisation, with all of the most recent issues
-  then filter out any of the issues that have been fixed
-  the construct the table params
-  then construct the template params
-  then render the template
-*/
+/**
+ * Middleware for handling issue tables.
+ * 
+ * This middleware:
+ * 1. Retrieves all entities for the organisation with their most recent issues
+ * 2. Filters out fixed issues
+ * 3. Constructs table parameters
+ * 4. Prepares template parameters
+ * 5. Renders the template
+ */

29-42: Consider more descriptive variable names for pagination

The pagination logic is solid, but consider renaming variables for better clarity:

  • minRowstartIndex
  • maxRowendIndex
   req.dataRange = {
-    minRow: (pageNumber - 1) * pageLength,
-    maxRow: Math.min((pageNumber - 1) * pageLength + pageLength, recordCount),
+    startIndex: (pageNumber - 1) * pageLength,
+    endIndex: Math.min((pageNumber - 1) * pageLength + pageLength, recordCount),
     totalRows: recordCount,
     maxPageNumber: Math.max(1, Math.ceil(recordCount / pageLength)),
     pageLength
   }

50-92: Consider breaking down complex mapping logic into smaller functions

The table preparation logic could be more maintainable by extracting the column mapping logic into separate functions.

+const createReferenceColumn = (entity, field, errorMessage, { lpa, dataset, issueType, issueField }, index) => ({
+  html: `<a href='/organisations/${encodeURIComponent(lpa)}/${encodeURIComponent(dataset)}/${encodeURIComponent(issueType)}/${encodeURIComponent(issueField)}/entry/${index + 1}'>${entity[field]}</a>`,
+  error: errorMessage ? { message: errorMessage } : undefined
+})
+
+const createRegularColumn = (entity, field, errorMessage) => ({
+  value: entity[field],
+  error: errorMessage ? { message: errorMessage } : undefined
+})
+
 export const prepareTableParams = (req, res, next) => {
   const { entities, issues, uniqueDatasetFields, dataRange } = req
   const { lpa, dataset, issue_type: issueType, issue_field: issueField } = req.params

   const allRows = entities.map((entity, index) => ({
     columns: Object.fromEntries(uniqueDatasetFields.map((field) => {
       const errorMessage = issues.find(issue => issue.entity === entity.entity && (issue.field === field || issue.replacement_field === field))?.issue_type
-      if (field === 'reference') {
-        return [field, {
-          html: `<a href='/organisations/${encodeURIComponent(lpa)}/${encodeURIComponent(dataset)}/${encodeURIComponent(issueType)}/${encodeURIComponent(issueField)}/entry/${index + 1}'>${entity[field]}</a>`,
-          error: errorMessage
-            ? {
-                message: errorMessage
-              }
-            : undefined
-        }]
-      } else {
-        return [field, {
-          value: entity[field],
-          error: errorMessage
-            ? {
-                message: errorMessage
-              }
-            : undefined
-        }]
-      }
+      return [
+        field,
+        field === 'reference'
+          ? createReferenceColumn(entity, field, errorMessage, { lpa, dataset, issueType, issueField }, index)
+          : createRegularColumn(entity, field, errorMessage)
+      ]
     }))
   }))

146-160: Add JSDoc types for template parameters

Consider adding JSDoc types to document the expected structure of template parameters.

+/**
+ * @typedef {Object} TemplateParams
+ * @property {Object} tableParams - Table configuration and data
+ * @property {Object} organisation - Organisation information
+ * @property {Object} dataset - Dataset information
+ * @property {Object} errorSummary - Error summary information
+ * @property {string} issueType - Type of issue being displayed
+ * @property {Object} pagination - Pagination configuration
+ * @property {Object} dataRange - Data range information
+ */

+/**
+ * Prepares parameters for the template rendering
+ * @param {Request} req - Express request object
+ * @param {Response} res - Express response object
+ * @param {Function} next - Express next middleware function
+ */
 export const prepareTemplateParams = (req, res, next) => {

164-177: Move redirection groups to configuration

Consider moving the hardcoded redirection groups to the configuration file for better maintainability.

-const redirectIssueGroups = [
+// Move to config/index.js
+export const issueRedirectGroups = [
   {
     type: 'missing value',
     field: 'reference'
   },
   // ...
 ]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4f21a53 and 9952399.

📒 Files selected for processing (2)
  • config/util.js (1 hunks)
  • src/middleware/issueTable.middleware.js (1 hunks)
🔇 Additional comments (5)
config/util.js (1)

95-96: Verify the configuration usage and consistency

Let's ensure this configuration is properly integrated and consistent with other pagination implementations in the codebase.

✅ Verification successful

Configuration is properly integrated and consistent

The tablePageLength configuration is well-integrated and aligns with existing pagination patterns:

  • It's properly defined in config/default.yaml with a default value of 50
  • The implementation in issueTable.middleware.js correctly uses this configuration for pagination
  • The value matches other pagination implementations (e.g., dataview.middleware.js also uses 50 as page length)
  • The pagination logic is consistent across different middleware implementations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of the new config property and other pagination implementations

# Check for usage of tablePageLength in the codebase
echo "Checking for tablePageLength usage:"
rg -l "tablePageLength"

# Check for other pagination implementations
echo "\nChecking for other pagination patterns:"
rg -l "pageLength|pageSize|itemsPerPage"

# Check for existing table configurations
echo "\nChecking for other table configurations:"
rg -l "table.*config"

Length of output: 723


Script:

#!/bin/bash
# Let's examine the actual implementation and usage of tablePageLength

# Check the configuration default value
echo "Default configuration:"
cat config/default.yaml

# Check the implementation in issueTable middleware
echo "\nIssue table middleware implementation:"
rg -A 5 -B 5 "tablePageLength" src/middleware/issueTable.middleware.js

# Check the tests to understand the expected behavior
echo "\nRelevant test cases:"
rg -A 5 -B 5 "tablePageLength" test/unit/middleware/issueTable.middleware.test.js

# Check other pagination implementations for consistency
echo "\nOther pagination implementations:"
rg -A 3 "pageLength|pageSize|itemsPerPage" src/middleware/dataview.middleware.js

Length of output: 5034

src/middleware/issueTable.middleware.js (4)

16-23: LGTM! Well-structured schema with proper validation

The schema correctly validates all required fields and includes proper type transformations.


44-48: LGTM! Proper URL encoding for security

The function correctly encodes all URL components to prevent XSS attacks.


193-211: LGTM! Well-structured middleware composition

The middleware chain is well-organized and follows a clear logical flow.


102-107: 🛠️ Refactor suggestion

Enhance error handling with custom error type

Consider using a custom error type for better error tracking and handling.

+class IssueTableError extends Error {
+  constructor(message, code) {
+    super(message);
+    this.name = 'IssueTableError';
+    this.code = code;
+  }
+}

   if (issues.length <= 0) {
     logger.warn(`issueTable was accessed from ${req.headers.referer} but there was no issues`)
-    const error = new Error('issue count must be larger than 0')
+    const error = new IssueTableError('No issues found to display in the table', 'NO_ISSUES_FOUND')
     return next(error)
   }

Likely invalid or redundant comment.

Comment thread config/util.js
Comment on lines +95 to +96
),
tablePageLength: v.number()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing the table pagination configuration

A few suggestions to improve the robustness and maintainability of the pagination configuration:

  1. Add validation constraints to ensure reasonable page sizes:
-  tablePageLength: v.number()
+  tablePageLength: v.pipe(
+    v.number(),
+    v.minValue(1),
+    v.maxValue(100)
+  )
  1. Consider moving this under a more specific namespace to improve maintainability as the config grows:
-  tablePageLength: v.number()
+  issueTable: v.object({
+    pageLength: v.pipe(
+      v.number(),
+      v.minValue(1),
+      v.maxValue(100)
+    )
+  })
  1. Consider a more specific property name to avoid potential conflicts with other table views that might be added in the future:
-  tablePageLength: v.number()
+  issueTablePageLength: v.pipe(
+    v.number(),
+    v.minValue(1),
+    v.maxValue(100)
+  )

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add issues table component to issues details page

4 participants