Skip to content

Commit c3797a1

Browse files
authored
Merge pull request #1129 from digital-land/efficient-fetchEntityIssueCounts
Efficient fetch entity issue counts
2 parents cf6e89e + 38f71e7 commit c3797a1

File tree

9 files changed

+109
-60
lines changed

9 files changed

+109
-60
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"test:integration": "NODE_ENV=test playwright test --config ./test/integration/playwright.config.js",
3333
"test:acceptance": "NODE_ENV=development playwright test --config ./test/acceptance/playwright.config.js",
3434
"test:acceptance:ci": "NODE_ENV=ci playwright test --config ./test/acceptance/playwright.config.js",
35-
"test:acceptance:local": "NODE_ENV=local playwright test --config ./test/acceptance/playwright.config.js",
35+
"test:acceptance:local": "NODE_ENV=local playwright test --config ./test/acceptance/playwright.config.js --ui",
3636
"playwright-codegen": "playwright codegen http://localhost:5000",
3737
"lint": "standard",
3838
"lint:fix": "standard --fix",

src/middleware/common.middleware.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,6 @@ export const fetchEntryIssues = fetchMany({
618618
export const fetchEntityIssueCounts = fetchMany({
619619
query: ({ req }) => {
620620
const datasetClause = req.params.dataset ? `AND i.dataset = '${req.params.dataset}'` : ''
621-
622621
return `
623622
WITH unique_issues AS (
624623
SELECT DISTINCT

src/middleware/lpa-overview.middleware.js

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,30 @@
55
*/
66

77
import performanceDbApi from '../services/performanceDbApi.js'
8-
import { expectationFetcher, expectations, fetchEndpointSummary, fetchEntityIssueCounts, fetchEntryIssueCounts, fetchOrgInfo, fetchResources, logPageError, noop, setAvailableDatasets } from './common.middleware.js'
9-
import { fetchMany, FetchOptions, renderTemplate, fetchOneFromAllDatasets } from './middleware.builders.js'
8+
import { expectationFetcher, expectations, fetchEndpointSummary, fetchEntryIssueCounts, fetchOrgInfo, fetchResources, logPageError, noop, setAvailableDatasets } from './common.middleware.js'
9+
import { fetchMany, FetchOptions, renderTemplate, fetchOneFromAllDatasets, parallel } from './middleware.builders.js'
1010
import { getDeadlineHistory, requiredDatasets } from '../utils/utils.js'
11-
import config from '../../config/index.js'
1211
import _ from 'lodash'
1312
import logger from '../utils/logger.js'
1413
import { isFeatureEnabled } from '../utils/features.js'
1514

1615
/**
17-
* Middleware. Updates req with 'datasetErrorStatus'.
16+
* Middleware. Updates req with 'entityIssueCounts' same as fetchEntityIssueCounts so not to be used together!
1817
*
19-
* Fetches datasets which have active endpoints in error state.
18+
* Functionally equivalent (for the utilization of the LPA Dashboard) to fetchEntityIssueCounts but using performanceDb
2019
*/
21-
const fetchDatasetErrorStatus = fetchMany({
20+
const fetchEntityIssueCountsPerformanceDb = fetchMany({
2221
query: ({ params }) => {
23-
return performanceDbApi.datasetErrorStatusQuery(params.lpa, { datasetsFilter: Object.keys(config.datasetsConfig) })
22+
return performanceDbApi.fetchEntityIssueCounts(params.lpa)
2423
},
25-
result: 'datasetErrorStatus',
24+
result: 'entityIssueCounts',
2625
dataset: FetchOptions.performanceDb
2726
})
2827

2928
const fetchProvisions = fetchMany({
3029
query: ({ params }) => {
31-
const excludeDatasets = Object.keys(config.datasetsConfig).map(dataset => `'${dataset}'`).join(',')
3230
return /* sql */ `select dataset, project, provision_reason
33-
from provision where organisation = '${params.lpa}' and dataset in (${excludeDatasets})`
31+
from provision where organisation = '${params.lpa}'`
3432
},
3533
result: 'provisions'
3634
})
@@ -285,6 +283,10 @@ export function prepareOverviewTemplateParams (req, res, next) {
285283
switch (reason) {
286284
case 'statutory':
287285
return 'statutory'
286+
case 'expected':
287+
return 'expected'
288+
case 'prospective':
289+
return 'prospective'
288290
default:
289291
return 'other'
290292
}
@@ -355,13 +357,17 @@ const fetchOutOfBoundsExpectations = expectationFetcher({
355357
* Organisation (LPA) overview page middleware chain.
356358
*/
357359
export default [
358-
fetchOrgInfo,
359-
fetchResources,
360-
fetchDatasetErrorStatus,
361-
fetchEndpointSummary,
362-
fetchEntityIssueCounts,
363-
fetchEntryIssueCounts,
364-
fetchEntityCounts,
360+
parallel([
361+
fetchOrgInfo,
362+
fetchResources,
363+
fetchEndpointSummary,
364+
fetchEntityIssueCountsPerformanceDb,
365+
fetchProvisions
366+
]),
367+
parallel([
368+
fetchEntryIssueCounts, // needs fetchResources to complete
369+
fetchEntityCounts // needs fetchOrgInfo to complete
370+
]),
365371
setAvailableDatasets,
366372
isFeatureEnabled('expectationOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop,
367373
groupResourcesByDataset,
@@ -372,7 +378,6 @@ export default [
372378

373379
// datasetSubmissionDeadlineCheck, // commented out as the logic is currently incorrect (https://github.com/digital-land/submit/issues/824)
374380
// addNoticesToDatasets, // commented out as the logic is currently incorrect (https://github.com/digital-land/submit/issues/824)
375-
fetchProvisions,
376381
prepareOverviewTemplateParams,
377382
getOverview,
378383
logPageError

src/routes/schemas.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export const OrgOverviewPage = v.strictObject({
136136
organisation: OrgField,
137137
datasets: v.object({
138138
statutory: v.optional(v.array(DatasetItem)),
139+
expected: v.optional(v.array(DatasetItem)),
140+
prospective: v.optional(v.array(DatasetItem)),
139141
other: v.optional(v.array(DatasetItem))
140142
}),
141143
totalDatasets: v.integer(),

src/services/performanceDbApi.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,22 @@ export default {
250250
AND pipeline = '${datasetId}'`
251251
},
252252

253+
// Query to simulate fetchEntityIssueCounts in common.middleware.js but against performanceDb
254+
fetchEntityIssueCounts (datasetId) {
255+
return /* sql */ `
256+
SELECT
257+
dataset,
258+
issue_type,
259+
field,
260+
COUNT(*) AS count
261+
FROM endpoint_dataset_issue_type_summary
262+
WHERE organisation = '${datasetId}'
263+
AND severity = 'error'
264+
AND responsibility = 'external'
265+
AND (resource_end_date = '' OR resource_end_date IS NULL)
266+
GROUP BY dataset, issue_type, field`
267+
},
268+
253269
datasetIssuesQuery,
254270

255271
/**

src/views/organisations/overview.html

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,17 @@ <h1 class="govuk-heading-xl">
9191
{% endif %}
9292
{% endfor %}
9393

94-
{% for dataset in datasets.other %}
95-
{% if dataset.notice %}
96-
{{ deadlineNotice(dataset.dataset, dataset.notice, organisation) }}
97-
{% endif %}
98-
{% endfor %}
94+
{% for dataset in datasets.expected %}
95+
{% if dataset.notice %}
96+
{{ deadlineNotice(dataset.dataset, dataset.notice, organisation) }}
97+
{% endif %}
98+
{% endfor %}
99+
100+
{% for dataset in datasets.prospective %}
101+
{% if dataset.notice %}
102+
{{ deadlineNotice(dataset.dataset, dataset.notice, organisation) }}
103+
{% endif %}
104+
{% endfor %}
99105

100106
</div>
101107
</div>
@@ -107,17 +113,17 @@ <h1 class="govuk-heading-xl">
107113
<div class="dataset-status">
108114
<div class="dataset-status--item">
109115
<span class="big-number">{{datasetsWithEndpoints}}/{{totalDatasets}}</span>
110-
datasets submitted
116+
authoritative dataset{{ "s" if (datasetsWithEndpoints != 1) }} provided
111117
</div>
112118

113119
<div class="dataset-status--item">
114120
<span class="big-number">{{datasetsWithErrors}}</span>
115-
dataset{{ "s" if (datasetsWithErrors != 1) }} with endpoint errors
121+
error{{ "s" if (datasetsWithErrors != 1) }} accessing URLs
116122
</div>
117123

118124
<div class="dataset-status--item">
119125
<span class="big-number">{{datasetsWithIssues}}</span>
120-
{{ "dataset" | pluralise(datasetsWithIssues) }} need{{ "s" if (datasetsWithIssues == 1) }} fixing
126+
{{ "dataset" | pluralise(datasetsWithIssues) }} can be improved
121127
</div>
122128
</div>
123129
</div>
@@ -126,10 +132,10 @@ <h1 class="govuk-heading-xl">
126132
<div class="govuk-grid-row">
127133
<div class="govuk-grid-column-two-thirds">
128134

129-
130135
{% if datasets.statutory.length > 0 %}
131136
<div data-testid="datasetsMandatory">
132137
<h2 class="govuk-heading-m">Datasets {{ organisation.name }} must provide</h2>
138+
<p class="org-membership-info">You are required to make these datasets available under legislation. Using the Planning Data Platform to do this can help you to create and maintain authoritative, trustworthy data.</p>
133139
<ul class="govuk-task-list govuk-!-margin-bottom-8" data-reason="statutory">
134140
{% for dataset in datasets.statutory %}
135141
{{ datasetItem(dataset) }}
@@ -138,22 +144,29 @@ <h2 class="govuk-heading-m">Datasets {{ organisation.name }} must provide</h2>
138144
</div>
139145
{% endif %}
140146

141-
{% if datasets.other.length > 0 %}
142-
<div data-testid="datasetsOdpMandatory">
143-
<h2 class="govuk-heading-m">Datasets organisations in Open Digital Planning programme must provide</h2>
144-
{% if isODPMember %}
145-
<p class="org-membership-info">{{ organisation.name}} is a member of the Open Digital Planning programme.</p>
146-
{% else %}
147-
<p class="org-membership-info">{{ organisation.name}} is not a member of the Open Digital Planning programme.</p>
148-
{% endif %}
149-
<ul class="govuk-task-list" data-reason="other">
150-
{% for dataset in datasets.other %}
147+
{% if datasets.expected.length > 0 and isODPMember %}
148+
<div data-testid="datasetsExpected">
149+
<h2 class="govuk-heading-m">Datasets {{ organisation.name}} are expected to provide</h2>
150+
<p class="org-membership-info">We expect you to provide these datasets as a condition of your funding with the Open Digital Planning community.</p>
151+
<ul class="govuk-task-list govuk-!-margin-bottom-8" data-reason="expected">
152+
{% for dataset in datasets.expected %}
151153
{{ datasetItem(dataset) }}
152154
{% endfor %}
153155
</ul>
154156
</div>
155157
{% endif %}
156158

159+
{% if datasets.prospective.length > 0 %}
160+
<div data-testid="datasetsProspective">
161+
<h2 class="govuk-heading-m">Datasets {{ organisation.name}} can provide</h2>
162+
<p class="org-membership-info">Your organisation is the authoritative source of this data. Providing this data to the platform helps ensure it is accurate and up to date.</p>
163+
<ul class="govuk-task-list" data-reason="prospective">
164+
{% for dataset in datasets.prospective %}
165+
{{ datasetItem(dataset) }}
166+
{% endfor %}
167+
</ul>
168+
</div>
169+
{% endif %}
157170

158171
</div>
159172
</div>

test/acceptance/dataset_overview.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ test.describe('Dataset overview', () => {
1919
expect(await page.locator('h1').innerText()).toEqual('London Borough of Lambeth overview')
2020
expect(await page.locator('[data-testid=datasetsMandatory] h2.govuk-heading-m').innerText())
2121
.toEqual('Datasets London Borough of Lambeth must provide')
22-
expect(await page.locator('[data-testid=datasetsMandatory] .govuk-task-list li').count()).toBeGreaterThanOrEqual(1)
23-
expect(await page.locator('[data-testid=datasetsOdpMandatory] h2.govuk-heading-m').innerText())
24-
.toEqual('Datasets organisations in Open Digital Planning programme must provide')
25-
expect(await page.locator('[data-testid=datasetsOdpMandatory] .govuk-task-list li').count()).toBeGreaterThanOrEqual(8)
22+
expect(await page.locator('[data-testid=datasetsExpected] .govuk-task-list li').count()).toBeGreaterThanOrEqual(1)
23+
expect(await page.locator('[data-testid=datasetsExpected] h2.govuk-heading-m').innerText())
24+
.toEqual('Datasets London Borough of Lambeth are expected to provide')
25+
expect(await page.locator('[data-testid=datasetsExpected] .govuk-task-list li').count()).toBeGreaterThanOrEqual(8)
2626
})
2727

2828
test('Can view dataset overview', async ({ page, context }) => {
@@ -99,7 +99,7 @@ test.describe('Dataset overview', () => {
9999
await organisationOverviewPage.navigateHere()
100100

101101
expect(await page.locator('h1').innerText()).toEqual('London Borough of Lambeth overview')
102-
const datasetElements = await page.locator('[data-testid=datasetsOdpMandatory] .govuk-task-list li').allInnerTexts()
102+
const datasetElements = await page.locator('[data-testid=datasetsExpected] .govuk-task-list li').allInnerTexts()
103103
const displayedNames = datasetElements
104104
.map(text => text.split('\n')[0].trim())
105105

test/unit/middleware/lpa-overview.middleware.test.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('lpa-overview.middleware', () => {
9696
{ endpointCount: 1, status: 'Live', dataset: 'dataset1', error: undefined, issueCount: 0, endpointErrorCount: 0 },
9797
{ endpointCount: 1, status: 'Error', dataset: 'dataset3', error: undefined, issueCount: 0, endpointErrorCount: 1 }
9898
]),
99-
other: expect.arrayContaining([
99+
expected: expect.arrayContaining([
100100
{ endpointCount: 0, status: 'Needs fixing', dataset: 'dataset2', error: undefined, issueCount: 1, endpointErrorCount: 0 },
101101
{ endpointCount: 0, status: 'Error', dataset: 'dataset4', error: 'There was a 404 error', issueCount: 0, endpointErrorCount: 1 }
102102
])
@@ -114,17 +114,23 @@ describe('lpa-overview.middleware', () => {
114114
expect(errorCardNodes[0].querySelector('.govuk-task-list__hint').textContent.trim()).toBe('There was an error accessing the endpoint URL')
115115
expect(errorCardNodes[1].querySelector('.govuk-task-list__hint').textContent.trim()).toBe('There was a 404 error')
116116

117-
const orgMemebershipInfo = doc.querySelector('.org-membership-info').textContent.trim()
118-
expect(orgMemebershipInfo).toMatch('is a member of the Open Digital Planning programme')
117+
// Check for ODP membership info in the expected datasets section
118+
const expectedSection = doc.querySelector('[data-testid="datasetsExpected"]')
119+
if (expectedSection) {
120+
const orgMembershipInfo = expectedSection.querySelector('.org-membership-info')
121+
expect(orgMembershipInfo.textContent.trim()).toMatch(/Open Digital Planning/)
122+
}
119123

120-
// verify proper label for non-OPD memebers gets rendered
124+
// verify proper label for non-ODP members gets rendered
121125
const reqNotMember = structuredClone(reqTemplate)
122126
reqNotMember.provisions.forEach((provision) => {
123127
provision.project = ''
124128
})
125129
prepareOverviewTemplateParams(reqNotMember, res, () => { })
126130
const { doc: docNotMember } = getRenderedErrorCards(reqNotMember.templateParams)
127-
expect(docNotMember.querySelector('.org-membership-info').textContent.trim()).toMatch('is not a member of the Open Digital Planning programme')
131+
// When not an ODP member, expected datasets won't render (requires isODPMember), so datasetsExpected won't be present
132+
const expectedSectionNotMember = docNotMember.querySelector('[data-testid="datasetsExpected"]')
133+
expect(expectedSectionNotMember).toBeNull()
128134
})
129135

130136
it('should patch dataset status based on the provision_summary info', () => {
@@ -139,7 +145,7 @@ describe('lpa-overview.middleware', () => {
139145
expect(ds1.status).toBe('Live')
140146
expect(ds1.error).toBeUndefined()
141147

142-
const ds4 = req.templateParams.datasets.other[1]
148+
const ds4 = req.templateParams.datasets.expected[1]
143149
expect(ds4.status).toBe('Error')
144150
expect(ds4.error).toBe(req.datasets[3].error) // Error message should be left untouched
145151
})

test/unit/views/organisations/lpaOverviewPage.test.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const datasetGroup = ({ expect }, key, datasets, document) => {
3838

3939
describe(`LPA Overview Page (seed: ${seed})`, () => {
4040
const params = mocker(OrgOverviewPage, seed)
41-
console.debug(`mocked datasets: statutory = ${params.datasets.statutory?.length ?? 'none'}, other = ${params.datasets.other?.length ?? 'none'}`)
41+
console.debug(`mocked datasets: statutory = ${params.datasets.statutory?.length ?? 'none'}, expected = ${params.datasets.expected?.length ?? 'none'}, prospective = ${params.datasets.prospective?.length ?? 'none'}`)
4242

4343
const html = nunjucks.render('organisations/overview.html', params)
4444

@@ -53,17 +53,18 @@ describe(`LPA Overview Page (seed: ${seed})`, () => {
5353
const statsBoxes = document.querySelector('.dataset-status').children
5454
it('Datasets provided gives the correct value', () => {
5555
expect(statsBoxes[0].textContent).toContain(`${params.datasetsWithEndpoints}/${params.totalDatasets}`)
56-
expect(statsBoxes[0].textContent).toContain('datasets submitted')
56+
expect(statsBoxes[0].textContent).toContain('authoritative dataset')
57+
expect(statsBoxes[0].textContent).toContain('provided')
5758
})
5859

5960
it('Datasets with errors gives the correct value', () => {
6061
expect(statsBoxes[1].textContent).toContain(params.datasetsWithErrors)
61-
expect(statsBoxes[1].textContent).toMatch(/datasets? with endpoint errors/)
62+
expect(statsBoxes[1].textContent).toContain('accessing URLs')
6263
})
6364

6465
it('Datasets with issues gives the correct value', () => {
6566
expect(statsBoxes[2].textContent).toContain(params.datasetsWithIssues)
66-
expect(statsBoxes[2].textContent).toMatch(/datasets? needs? fixing/)
67+
expect(statsBoxes[2].textContent).toContain('can be improved')
6768
})
6869

6970
it('The correct number of dataset cards are rendered with the correct titles in group "statutory"', () => {
@@ -72,16 +73,23 @@ describe(`LPA Overview Page (seed: ${seed})`, () => {
7273
}
7374
})
7475

75-
it('The correct number of dataset cards are rendered with the correct titles in group "other"', () => {
76-
if (params.datasets.other && params.datasets.other.length > 0) {
77-
datasetGroup({ expect }, 'other', params.datasets.other, document)
76+
it('The correct number of dataset cards are rendered with the correct titles in group "expected"', () => {
77+
if (params.datasets.expected && params.datasets.expected.length > 0) {
78+
datasetGroup({ expect }, 'expected', params.datasets.expected, document)
79+
}
80+
})
7881

79-
const infoText = document.querySelector('.org-membership-info').textContent
80-
expect(infoText).match(new RegExp(`${params.organisation.name} is (a|not a) member of .*`))
82+
it('The correct number of dataset cards are rendered with the correct titles in group "prospective"', () => {
83+
if (params.datasets.prospective && params.datasets.prospective.length > 0) {
84+
datasetGroup({ expect }, 'prospective', params.datasets.prospective, document)
8185
}
8286
})
8387

84-
const allDatasets = [].concat(...Object.values(params.datasets.statutory ?? []), ...Object.values(params.datasets.other ?? []))
88+
const allDatasets = [
89+
...(params.datasets.statutory ?? []),
90+
...(params.datasets.expected ?? []),
91+
...(params.datasets.prospective ?? [])
92+
]
8593
allDatasets.forEach((dataset, i) => {
8694
it(`dataset cards are rendered with correct hints for dataset='${dataset.dataset}'`, () => {
8795
let expectedHint = 'Endpoint URL submitted'

0 commit comments

Comments
 (0)