Skip to content

Commit b821c18

Browse files
sadiqkhojaspwoodcock
authored andcommitted
Return lastUpdate for the dataset endpoints (getodk#1420)
* Feature getodk/central#803: return lastUpdate for the dataset endpoints * Return lastUpdate using getLastUpdateTimestamp instead
1 parent 55b07fe commit b821c18

File tree

4 files changed

+58
-33
lines changed

4 files changed

+58
-33
lines changed

docs/api.yaml

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ info:
5353
- `webformsEnabled` boolean flag is added to [Forms](/central-api-form-management) APIs that enables use of ODK Web Forms instead of Enketo.
5454
- Draft Submission can be created using draft token by adding `/test/{token}` to [Creating a draft Submission](/central-api-submission-management/#id1) endpoint.
5555

56+
**Changed**:
57+
- [Dataset Metadata](/central-api-dataset-management/#dataset-metadata) now includes a new property, `lastUpdate`, which is the timestamp of the latest modification to the datasets or its entities.
58+
59+
5660
## ODK Central v2024.3
5761

5862
**Added**:
@@ -8601,14 +8605,13 @@ paths:
86018605
content:
86028606
application/json:
86038607
schema:
8604-
type: array
8605-
items:
8606-
$ref: '#/components/schemas/DatasetMetadata'
8608+
$ref: '#/components/schemas/DatasetMetadata'
86078609
example:
86088610
name: people
86098611
createdAt: '2018-01-19T23:58:03.395Z'
86108612
projectId: 1
86118613
approvalRequired: false
8614+
lastUpdate: '2018-01-19T23:58:03.395Z'
86128615
sourceForms: []
86138616
linkedForms: []
86148617
properties: []
@@ -8663,14 +8666,13 @@ paths:
86638666
content:
86648667
application/json:
86658668
schema:
8666-
type: array
8667-
items:
8668-
$ref: '#/components/schemas/DatasetMetadata'
8669+
$ref: '#/components/schemas/DatasetMetadata'
86698670
example:
86708671
name: people
86718672
createdAt: '2018-01-19T23:58:03.395Z'
86728673
projectId: 1
86738674
approvalRequired: true
8675+
lastUpdate: '2020-10-10T03:04:51.695Z'
86748676
sourceForms:
86758677
- xmlFormId: treeRegistration
86768678
name: Tree Registration
@@ -8732,14 +8734,13 @@ paths:
87328734
content:
87338735
application/json:
87348736
schema:
8735-
type: array
8736-
items:
8737-
$ref: '#/components/schemas/DatasetMetadata'
8737+
$ref: '#/components/schemas/DatasetMetadata'
87388738
example:
87398739
name: people
87408740
createdAt: '2018-01-19T23:58:03.395Z'
87418741
projectId: 1
87428742
approvalRequired: true
8743+
lastUpdate: '2020-10-10T03:04:51.695Z'
87438744
sourceForms:
87448745
- xmlFormId: treeRegistration
87458746
name: Tree Registration
@@ -12922,22 +12923,29 @@ components:
1292212923
items:
1292312924
$ref: '#/components/schemas/FormKeyValue'
1292412925
DatasetMetadata:
12925-
properties:
12926-
linkedForms:
12927-
type: array
12928-
items:
12929-
$ref: '#/components/schemas/FormKeyValue'
12930-
description: Forms that consume data from the Dataset
12931-
sourceForms:
12932-
type: array
12933-
items:
12934-
$ref: '#/components/schemas/FormKeyValue'
12935-
description: Forms that create Entities in the Dataset
12936-
properties:
12937-
type: array
12938-
description: All properties of the Dataset
12939-
items:
12940-
$ref: '#/components/schemas/PropertyDetailed'
12926+
allOf:
12927+
- $ref: '#/components/schemas/Dataset'
12928+
- type: object
12929+
properties:
12930+
lastUpdate:
12931+
type: string
12932+
description: ISO date format. The timestamp of the most recent change to the dataset or its entities
12933+
example: '2020-10-10T03:04:51.695Z'
12934+
linkedForms:
12935+
type: array
12936+
items:
12937+
$ref: '#/components/schemas/FormKeyValue'
12938+
description: Forms that consume data from the Dataset
12939+
sourceForms:
12940+
type: array
12941+
items:
12942+
$ref: '#/components/schemas/FormKeyValue'
12943+
description: Forms that create Entities in the Dataset
12944+
properties:
12945+
type: array
12946+
description: All properties of the Dataset
12947+
items:
12948+
$ref: '#/components/schemas/PropertyDetailed'
1294112949
Submission:
1294212950
type: object
1294312951
required:

lib/model/frames/dataset.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const Dataset = Frame.define(
2626

2727
Dataset.Extended = class extends Frame.define(
2828
'entities', readable, 'lastEntity', readable,
29-
'conflicts', readable
29+
'conflicts', readable
3030
) {
3131
// default these properties to 0, since sql gives null if they're 0.
3232
forApi() {

lib/model/query/datasets.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ const _get = extender(Dataset)(Dataset.Extended)((fields, extend, options, publi
361361
SELECT ${fields} FROM datasets
362362
${extend|| sql`
363363
LEFT JOIN (
364-
SELECT "datasetId", COUNT(1) "entities", MAX(COALESCE("updatedAt", "createdAt")) "lastEntity", COUNT(1) FILTER (WHERE conflict IS NOT NULL) "conflicts"
364+
SELECT "datasetId", COUNT(1) "entities", MAX("createdAt") "lastEntity", COUNT(1) FILTER (WHERE conflict IS NOT NULL) "conflicts"
365365
FROM entities e
366366
WHERE "deletedAt" IS NULL
367367
GROUP BY "datasetId"
@@ -455,6 +455,7 @@ const getMetadata = (dataset) => async ({ all, Datasets }) => {
455455
const properties = await Datasets.getProperties(dataset.id, true);
456456
const linkedForms = await all(_getLinkedForms(dataset.name, dataset.projectId));
457457
const sourceForms = await all(_getSourceForms(dataset.name, dataset.projectId));
458+
const lastUpdate = await Datasets.getLastUpdateTimestamp(dataset.id);
458459

459460
const propertiesMap = new Map();
460461
for (const property of properties) {
@@ -483,6 +484,7 @@ const getMetadata = (dataset) => async ({ all, Datasets }) => {
483484
// return all dataset metadata
484485
return {
485486
...dataset.forApi(),
487+
lastUpdate,
486488
linkedForms,
487489
sourceForms,
488490
properties: Array.from(propertiesMap.values())
@@ -654,7 +656,8 @@ const getUnprocessedSubmissions = (datasetId) => ({ all }) =>
654656
all(_unprocessedSubmissions(datasetId, sql`audits.*`))
655657
.then(map(construct(Audit)));
656658

657-
// Used in the openRosa form attachment manifest
659+
// Used in the openRosa form attachment manifest.
660+
// Also to return lastUpdate field for `GET /datasets/:name`
658661
const getLastUpdateTimestamp = (datasetId) => ({ maybeOne }) =>
659662
maybeOne(sql`
660663
SELECT "loggedAt" FROM audits

test/integration/api/datasets.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ describe('datasets and entities', () => {
607607
.expect(200)
608608
.then(({ body }) => {
609609
body[0].should.be.an.ExtendedDataset();
610-
body.map(({ createdAt, lastEntity, ...d }) => d).should.eql([
610+
body.map(({ createdAt, lastEntity, lastUpdate, ...d }) => d).should.eql([
611611
{ name: 'people', projectId: 1, entities: 1, approvalRequired: false, conflicts: 0 }
612612
]);
613613
});
@@ -678,6 +678,12 @@ describe('datasets and entities', () => {
678678

679679
await exhaust(container);
680680

681+
await asAlice.patch('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc?force=true')
682+
.send({
683+
label: 'Alice - updated'
684+
})
685+
.expect(200);
686+
681687
await asAlice.get('/v1/projects/1/datasets')
682688
.set('X-Extended-Metadata', 'true')
683689
.expect(200)
@@ -729,7 +735,7 @@ describe('datasets and entities', () => {
729735

730736
await exhaust(container);
731737

732-
await container.run(sql`UPDATE entities SET "createdAt" = '1999-1-1T00:00:00Z' WHERE TRUE`);
738+
await container.run(sql`UPDATE entities SET "createdAt" = '1999-1-1T00:00:00Z', "updatedAt" = '1999-1-1T00:00:00Z' WHERE TRUE`);
733739

734740
await asAlice.post('/v1/projects/1/forms/simpleEntity/submissions')
735741
.send(testData.instances.simpleEntity.two)
@@ -1358,7 +1364,7 @@ describe('datasets and entities', () => {
13581364
.expect(200)
13591365
.then(({ body }) => {
13601366

1361-
const { createdAt, linkedForms, properties, sourceForms, ...ds } = body;
1367+
const { createdAt, linkedForms, properties, sourceForms, lastUpdate, ...ds } = body;
13621368

13631369
ds.should.be.eql({
13641370
name: 'people',
@@ -1368,6 +1374,8 @@ describe('datasets and entities', () => {
13681374

13691375
createdAt.should.not.be.null();
13701376

1377+
lastUpdate.should.be.isoDate();
1378+
13711379
linkedForms.should.be.eql([{ name: 'withAttachments', xmlFormId: 'withAttachments' }]);
13721380

13731381
sourceForms.should.be.eql([
@@ -1407,12 +1415,18 @@ describe('datasets and entities', () => {
14071415
})
14081416
.expect(200);
14091417

1418+
await asAlice.patch('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-111111111aaa?force=true')
1419+
.send({
1420+
label: 'Johnny Doe - updated'
1421+
})
1422+
.expect(200);
1423+
14101424
await asAlice.get('/v1/projects/1/datasets/people')
14111425
.set('X-Extended-Metadata', 'true')
14121426
.expect(200)
14131427
.then(({ body }) => {
14141428

1415-
const { createdAt, properties, lastEntity, ...ds } = body;
1429+
const { createdAt, properties, lastEntity, lastUpdate, ...ds } = body;
14161430

14171431
ds.should.be.eql({
14181432
name: 'people',
@@ -1425,7 +1439,7 @@ describe('datasets and entities', () => {
14251439
});
14261440

14271441
lastEntity.should.be.recentIsoDate();
1428-
1442+
lastUpdate.should.be.recentIsoDate();
14291443
createdAt.should.be.recentIsoDate();
14301444

14311445
properties.map(({ publishedAt, ...p }) => {

0 commit comments

Comments
 (0)