Skip to content

Commit

Permalink
Fix importing ordered items
Browse files Browse the repository at this point in the history
  • Loading branch information
u12206050 committed Feb 5, 2024
1 parent 48a7d8d commit a7fa6f5
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 4 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# Version 1.6.2
# Version 1.6.3

- Add `linkedFields` to fix inserting ordered items that might depend on each other.
- Specifically fixes importing of flow operations, update the directus_config and add `linkedFields`.
```javascript
{
directus_operations: {
watch: ['operations'],
excludeFields: ['user_created'],
linkedFields: ['resolve', 'reject'],
query: {
filter: {
flow: { trigger: { _neq: 'webhook' } },
},
},
},
}
```
- Also fix auto IMPORT issue when mv_ts is null.

## Version 1.6.2

- (postgres only) Fixed import issue due to default_value containing functions for auto increment

Expand Down
2 changes: 2 additions & 0 deletions install/schema-sync/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* collectionName:
* watch: array of events to watch for changes, eg. 'posts.items',
* excludeFields: (optional) array of fields to exclude from the export,
* m2oFields: (optional) array of fields to treat as many-to-one relationships for hierarchy, eg. ['parent'],
* getKey: (optional) function to get the key for the item, defaults to primary key found on schema,
* query: (optional) query to use when exporting the collection, valid options are: (limit=-1 | filter | sort)
* prefix: (optional) prefix the exported json file with this string (useful for separating test data from production data)
Expand All @@ -16,6 +17,7 @@ export const syncCustomCollections = {
posts: {
watch: ['posts.items'],
excludeFields: ['user_created', 'user_updated'],
m2oFields: ['parent'],
query: {
filter: {
shared: { _eq: true }
Expand Down
1 change: 1 addition & 0 deletions install/schema-sync/directus_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const syncDirectusCollections = {
directus_operations: {
watch: ['operations'],
excludeFields: ['user_created'],
linkedFields: ['resolve', 'reject'],
query: {
filter: {
flow: { trigger: { _neq: 'webhook' } },
Expand Down
62 changes: 60 additions & 2 deletions src/collectionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ class CollectionExporter implements IExporter {
await writeFile(this.filePath, json);
}

protected _settings: {
inclFields: Array<string>;
exclFields: Array<string>;
linkedFields: NonNullable<CollectionExporterOptions['linkedFields']>
getKey: (o: Item) => PrimaryKey;
getPrimary: (o: Item) => PrimaryKey;
query: Query;
queryWithPrimary: Query;
} | null = null;
protected async settings() {
if (this._settings) return this._settings;

const itemsSvc = await this._getService()
const schema = itemsSvc.schema.collections[this.collection]

Expand Down Expand Up @@ -95,9 +106,10 @@ class CollectionExporter implements IExporter {

const queryWithPrimary: Query = exclFields.includes(schema.primary) ? { ...query, fields: [...inclFields, schema.primary] } : query;

return {
return this._settings = {
inclFields,
exclFields,
linkedFields: this.options.linkedFields || [],
getKey,
getPrimary,
query,
Expand Down Expand Up @@ -125,6 +137,46 @@ class CollectionExporter implements IExporter {
return JSON.stringify(sortObject(items), null, 2);
}


/**
* Orders items so that items that are linked are inserted after the items they reference
* Only works with items that have a primary key
* Assumes items not in given items list are already in the database
* @param items
* @returns
*/
protected async sortbyIfLinked(items: Array<Item>) {
const { getPrimary, linkedFields } = await this.settings();
if (!linkedFields.length) return false;

const itemsMap = items.reduce((map, o) => {
o.__dependents = [];
map[getPrimary(o)] = o;
return map;
}, {} as Record<PrimaryKey, Item>);

let hasDependents = false;
items.forEach(o => {
for (const fieldName of linkedFields) {
const value = o[fieldName];
if (value && itemsMap[value]) {
hasDependents = true;
itemsMap[value].__dependents.push(o);
}
}
});

items.sort((a, b) => this.countDependents(b) - this.countDependents(a));
items.forEach(o => delete o.__dependents);

return true;
}
// Recursively count dependents
private countDependents(o: any): number {
if (!o.__dependents.length) return 0;
return o.__dependents.reduce((acc, o) => acc + this.countDependents(o), o.__dependents.length);
}

public async loadJSON(json: JSONString | null, merge = false) {
if (!json) return null;
const loadedItems = JSON.parse(json) as Array<Item>;
Expand Down Expand Up @@ -182,7 +234,13 @@ class CollectionExporter implements IExporter {
// Insert
if (toInsert.length > 0) {
this.logger.debug(`Inserting ${toInsert.length} x ${this.collection} items`);
await itemsSvc.createMany(toInsert);
if (await this.sortbyIfLinked(toInsert)) {
for (const item of toInsert) {
await itemsSvc.createOne(item);
}
} else {
await itemsSvc.createMany(toInsert);
}
}

// Update
Expand Down
1 change: 1 addition & 0 deletions src/default_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const syncDirectusCollections: ExportCollectionConfig = {
directus_operations: {
watch: ['operations'],
excludeFields: ['user_created'],
linkedFields: ['resolve', 'reject'],
},
directus_translations: {
watch: ['translations'],
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type ExportCollectionConfig = Record<

export type CollectionExporterOptions = {
excludeFields?: string[];
linkedFields?: string[];
getKey?: (o: Item) => PrimaryKey;
query?: Pick<Query, 'filter'|'sort'|'limit'>;
prefix?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/updateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class UpdateManager {
// Only need to migrate if hash is different
.andWhereNot('mv_hash', newHash)
// And only if the previous hash is older than the current one
.andWhere('mv_ts', '<', isoTS)
.andWhere('mv_ts', '<', isoTS).orWhereNull('mv_ts')

This comment has been minimized.

Copy link
@adelinn

adelinn Feb 15, 2024

Contributor

this will translate to
(

  • (
    • (id = 1) AND
    • (NOT mv_locked) AND
    • ((mv_hash)::text <> '9628927b......ad1e8'::text) AND
    • (mv_ts < '2020-01-01 00:00:01+00'::timestamp with time zone)
  • ) OR
  • (mv_ts IS NULL)
    )
    (from result of EXPLAIN command)

This means that field will be considered as ready to be locked regardless of id or mv_locked status which means that if the db has mv_ts null loking the row will not work.

.forUpdate(); // This locks the row

// If row is found, lock it
Expand Down

0 comments on commit a7fa6f5

Please sign in to comment.