-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consolidate content versioning #22413
base: main
Are you sure you want to change the base?
Changes from all commits
1ca7a89
0215517
7ff19fb
ca86cb0
da645fa
f2f757d
d50f17a
06a97d3
c0ad03f
4fed821
1c529f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
'@directus/system-data': patch | ||
'@directus/types': patch | ||
'@directus/api': patch | ||
--- | ||
|
||
Consolidated content versioning with a new `delta` field in `directus_versions` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import type { Knex } from 'knex'; | ||
import { assign, sortBy } from 'lodash-es'; | ||
|
||
export async function up(knex: Knex): Promise<void> { | ||
await knex.schema.alterTable('directus_versions', (table) => { | ||
table.json('delta'); | ||
}); | ||
|
||
const versions = await knex.select('id').from('directus_versions'); | ||
|
||
for (const version of versions) { | ||
const deltas = sortBy( | ||
await knex.select('id', 'delta').from('directus_revisions').where('version', '=', version.id), | ||
'id', | ||
).map((revision) => (typeof revision.delta === 'string' ? JSON.parse(revision.delta) : revision.delta)); | ||
|
||
const finalVersionDelta = assign({}, ...deltas); | ||
|
||
await knex('directus_versions') | ||
.update({ delta: JSON.stringify(finalVersionDelta) }) | ||
.where('id', '=', version.id); | ||
} | ||
} | ||
|
||
export async function down(knex: Knex): Promise<void> { | ||
await knex.schema.alterTable('directus_versions', (table) => { | ||
table.dropColumn('delta'); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,10 @@ export class VersionsService extends ItemsService { | |
|
||
if (!versions?.[0]) return null; | ||
|
||
if (versions[0]['delta']) { | ||
return [versions[0]['delta']]; | ||
} | ||
|
||
const saves = await this.getVersionSavesById(versions[0]['id']); | ||
|
||
return saves; | ||
|
@@ -167,10 +171,11 @@ export class VersionsService extends ItemsService { | |
} | ||
|
||
override async updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]> { | ||
// Only allow updates on "key" and "name" fields | ||
// Only allow updates on "key", "name" and "delta" fields | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should handle Currently, would also do unnecessary duplicate work when called via If we should keep it here, reference docs need to be updated 1 where we would need to make clear what the differences are. Footnotes |
||
const versionUpdateSchema = Joi.object({ | ||
key: Joi.string(), | ||
name: Joi.string().allow(null).optional(), | ||
delta: Joi.object().optional(), | ||
}); | ||
|
||
const { error } = versionUpdateSchema.validate(data); | ||
|
@@ -253,17 +258,29 @@ export class VersionsService extends ItemsService { | |
delta: revisionDelta, | ||
}); | ||
|
||
let existingDelta = version['delta']; | ||
|
||
if (!existingDelta) { | ||
const saves = await this.getVersionSavesById(key); | ||
|
||
existingDelta = assign({}, ...saves); | ||
} | ||
|
||
const finalVersionDelta = assign({}, existingDelta, revisionDelta ? JSON.parse(revisionDelta) : null); | ||
|
||
await this.updateOne(key, { delta: finalVersionDelta }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Saving a new version now requires update permission on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm leaning towards the breaking change by requiring admins to update permissions. 🤔 |
||
|
||
const { cache } = getCache(); | ||
|
||
if (shouldClearCache(cache, undefined, collection)) { | ||
cache.clear(); | ||
} | ||
|
||
return data; | ||
return finalVersionDelta; | ||
} | ||
|
||
async promote(version: PrimaryKey, mainHash: string, fields?: string[]) { | ||
const { id, collection, item } = (await this.readOne(version)) as ContentVersion; | ||
const { id, collection, item, delta } = (await this.readOne(version)) as ContentVersion; | ||
|
||
// will throw an error if the accountability does not have permission to update the item | ||
await this.authorizationService.checkAccess('update', collection, item); | ||
|
@@ -276,9 +293,15 @@ export class VersionsService extends ItemsService { | |
}); | ||
} | ||
|
||
const saves = await this.getVersionSavesById(id); | ||
let versionResult; | ||
|
||
const versionResult = assign({}, ...saves); | ||
if (delta) { | ||
versionResult = delta; | ||
} else { | ||
const saves = await this.getVersionSavesById(id); | ||
|
||
versionResult = assign({}, ...saves); | ||
} | ||
|
||
const payloadToUpdate = fields ? pick(versionResult, fields) : versionResult; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,3 +36,7 @@ fields: | |
- field: user_updated | ||
special: | ||
- user-updated | ||
|
||
- field: delta | ||
special: | ||
- cast-json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels strange, that this method now (in most/all cases?) returns only one item (the
delta
), especially while it is calledgetVersionSaves
.Do we even need that method and
getVersionSavesById
anymore? Can't we just always use thedelta
? I think the associated revision items now only serve the purpose of history, but not as source of truth anymore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
getVersionSavesById
is just for the edge case where a new version is created but on the older system when the migration is being run. All usages of it are part of the else clause where thedelta
is missing. 🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we should/can account for this edge case. I think this would mean we could never really get rid of it. In any case, I think this method needs some clean-up, for the first reason stated above.