-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- with a temporary script to create ordinals on existing elements - allow to update ordinals, by accepting paginated elements subset for performance and client friendlyness - should test that removing element then reordering is ok
- Loading branch information
Showing
8 changed files
with
261 additions
and
7 deletions.
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
scripts/db_actions/assign_elements_ordinal_to_all_listings.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#!/usr/bin/env tsx | ||
// Temporary updater for assigning elements `ordinal` to elements documents which do not have one | ||
import { compact, property, sortBy } from 'lodash-es' | ||
import { getElementsByListings } from '#controllers/listings/lib/elements' | ||
import dbFactory from '#db/couchdb/base' | ||
import { isNonEmptyArray } from '#lib/boolean_validations' | ||
import { wait } from '#lib/promises' | ||
import { updateElementDoc } from '#models/element' | ||
import { shellExec } from '#scripts/scripts_utils' | ||
import config from '#server/config' | ||
|
||
const dbBaseUrl = config.db.getOrigin() | ||
|
||
const assignElementsOrdinalToAllListings = async () => { | ||
const getAllListingsIdsCurlCommand = `curl -H 'Content-Type:application/json' -H 'Accept: application/json' '${dbBaseUrl}/${config.db.name('lists')}/_all_docs?include_docs=true' | jq '.rows[] | .doc._id' | jq -s` | ||
const { stdout } = await shellExec(getAllListingsIdsCurlCommand) | ||
const allListingsIds = JSON.parse(stdout).slice(0, -2) // remove last array item `"_design/lists"` | ||
await addListingsOrdinals(allListingsIds) | ||
process.exit(0) | ||
} | ||
|
||
function areSomeWithoutOrdinal (elements) { | ||
return elements.find(el => (typeof (el.ordinal) === 'undefined')) | ||
} | ||
|
||
async function updateOrdinals (elements) { | ||
const orderedUris = elements.map(property('uri')) | ||
return reorderElements(orderedUris, elements) | ||
} | ||
|
||
const addListingOrdinals = async listingId => { | ||
const elements = await getElementsByListings([ listingId ]) | ||
if (isNonEmptyArray(elements)) { | ||
if (areSomeWithoutOrdinal(elements)) { | ||
console.log('##### 33 assign_elements_ordinal_to_all_listings.ts elements', elements) | ||
// Just making sure | ||
const sortedElements = sortBy(elements, 'created') | ||
console.log('updating listing:', listingId) | ||
return updateOrdinals(sortedElements) | ||
} | ||
} | ||
} | ||
|
||
async function addListingsOrdinals (listingsIds) { | ||
const remainingListingsIds = listingsIds.slice(0) // clone | ||
const nextBatch = async () => { | ||
// One by one since, updating all elements from a listing may be heavy | ||
const batchListingsIds = remainingListingsIds.splice(0, 1) | ||
if (batchListingsIds.length === 0) return | ||
await Promise.all(compact(batchListingsIds.map(addListingOrdinals))) | ||
// Give couchdb some rest | ||
// await wait(1000) | ||
return nextBatch() | ||
} | ||
await nextBatch() | ||
} | ||
|
||
// Do not reuse reorderElements from controllers/listings/lib/elements.ts to be able to also update elements without ordinal. See reorderAndUpdateDocs below | ||
const db = await dbFactory('elements') | ||
|
||
export async function reorderElements (uris, currentElements) { | ||
const docsToUpdate = reorderAndUpdateDocs({ | ||
updateDocFn: updateElementDoc, | ||
newOrderedKeys: uris, | ||
currentDocs: currentElements, | ||
attributeToSortBy: 'uri', | ||
indexKey: 'ordinal', | ||
}) | ||
if (docsToUpdate.length > 0) { | ||
await db.bulk(docsToUpdate) | ||
} | ||
} | ||
|
||
function reorderAndUpdateDocs ({ updateDocFn, newOrderedKeys, currentDocs, attributeToSortBy, indexKey }) { | ||
const docsToUpdate = [] | ||
for (let i = 0; i < newOrderedKeys.length; i++) { | ||
const currentDoc = currentDocs.find(el => el[attributeToSortBy] === newOrderedKeys[i]) | ||
// Next line is different than in reorderAndUpdateDocs | ||
// to be able to assign new `ordinal` key | ||
if (currentDoc[indexKey] === undefined || currentDoc[indexKey] !== i) { | ||
const newAttributes = {} | ||
newAttributes[indexKey] = i | ||
docsToUpdate.push(updateDocFn(newAttributes, currentDoc)) | ||
} | ||
} | ||
return docsToUpdate | ||
} | ||
|
||
assignElementsOrdinalToAllListings() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { reorderElements } from '#controllers/listings/lib/elements' | ||
import { getListingWithElements, validateListingsOwnership, validateElementsUrisInListing } from '#controllers/listings/lib/listings' | ||
import { newError } from '#lib/error/error' | ||
|
||
const sanitization = { | ||
id: {}, | ||
uris: {}, | ||
} | ||
|
||
const controller = async ({ id, uris, reqUserId }) => { | ||
const listing = await getListingWithElements(id) | ||
validateListingsOwnership(reqUserId, [ listing ]) | ||
const elements = listing.elements | ||
if (!elements || elements.length === 0) { | ||
throw newError('no elements to reorder', 400, { list: listing }) | ||
} | ||
validateElementsUrisInListing(uris, elements) | ||
await reorderElements(uris, elements) | ||
const resListing = await getListingWithElements(id) | ||
return { | ||
ok: true, | ||
list: resListing, | ||
} | ||
} | ||
|
||
export default { | ||
sanitization, | ||
controller, | ||
track: [ 'lists', 'reorder' ], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,91 @@ | ||
import { createListingWithElements } from '../fixtures/listings.js' | ||
import { map } from 'lodash-es' | ||
import { getByIdWithElements } from '#tests/api/utils/listings' | ||
import { shouldNotBeCalled, rethrowShouldNotBeCalledErrors } from '#tests/unit/utils' | ||
import { createListing, createListingWithElements, createElement, removeElement } from '../fixtures/listings.js' | ||
import { authReq } from '../utils/utils.js' | ||
|
||
const endpoint = '/api/lists?action=' | ||
const reorder = `${endpoint}reorder` | ||
|
||
describe('element:listing-ordinal', () => { | ||
it('should create elements with a listingId', async () => { | ||
const { listing } = await createListingWithElements() | ||
listing.elements[0].ordinal.should.equal(0) | ||
listing.elements[1].ordinal.should.equal(1) | ||
}) | ||
|
||
it('should reject without elements to reorder', async () => { | ||
const { listing } = await createListingWithElements() | ||
try { | ||
await authReq('post', reorder, { id: listing._id }) | ||
.then(shouldNotBeCalled) | ||
} catch (err) { | ||
rethrowShouldNotBeCalledErrors(err) | ||
err.body.status_verbose.should.equal('missing parameter in body: uris') | ||
err.statusCode.should.equal(400) | ||
} | ||
}) | ||
|
||
it('should reject listing without elements', async () => { | ||
const { listing } = await createListing() | ||
const { element } = await createElement({}) | ||
try { | ||
await authReq('post', reorder, { | ||
id: listing._id, | ||
uris: [ element.uri ], | ||
}) | ||
.then(shouldNotBeCalled) | ||
} catch (err) { | ||
rethrowShouldNotBeCalledErrors(err) | ||
err.body.status_verbose.should.equal('no elements to reorder') | ||
err.statusCode.should.equal(400) | ||
} | ||
}) | ||
|
||
it('should reject with elements not belonging to a listing', async () => { | ||
const { listing } = await createListingWithElements() | ||
const { element } = await createElement({}) | ||
try { | ||
await authReq('post', reorder, { | ||
id: listing._id, | ||
uris: [ element.uri ], | ||
}) | ||
.then(shouldNotBeCalled) | ||
} catch (err) { | ||
rethrowShouldNotBeCalledErrors(err) | ||
err.body.status_verbose.should.equal('some elements are not in the list') | ||
err.statusCode.should.equal(400) | ||
} | ||
}) | ||
|
||
it('should reorder elements', async () => { | ||
const { listing } = await createListingWithElements() | ||
const [ uri1, uri2 ] = map(listing.elements, 'uri') | ||
await authReq('post', reorder, { | ||
id: listing._id, | ||
uris: [ uri2, uri1 ], | ||
}) | ||
const resListing = await getByIdWithElements({ id: listing._id }) | ||
resListing.elements[0].uri.should.equal(uri2) | ||
resListing.elements[1].ordinal.should.equal(1) | ||
}) | ||
|
||
it('should assign new ordinal if one element has been removed', async () => { | ||
const { listing, uris } = await createListingWithElements() | ||
const [ uri1, uri2 ] = uris | ||
const { uri: uri3 } = await createElement({ listing }) | ||
await removeElement({ uri: uri2, listing }) | ||
|
||
const resListing = await getByIdWithElements({ id: listing._id }) | ||
resListing.elements[1].uri.should.equal(uri3) | ||
resListing.elements[1].ordinal.should.equal(2) | ||
|
||
await authReq('post', reorder, { | ||
id: listing._id, | ||
uris: [ uri1, uri3 ], | ||
}) | ||
const resListing2 = await getByIdWithElements({ id: listing._id }) | ||
resListing2.elements[1].uri.should.equal(uri3) | ||
resListing2.elements[1].ordinal.should.equal(1) | ||
}) | ||
}) |