mirror of
https://github.com/directus/directus.git
synced 2026-01-23 09:58:10 -05:00
Allow disabling activity/revisions (#5112)
* Add accountability column * Add field info for accountability * Add accountability to collection type * Fetch accountability info from collection meta * Add field name translation for accountability field * Hide revisions drawer detail if revisions aren't available * Only save activity where accountability flag matches * Disable revisions for directus_presets Fixes #3767 * Tweak field option naming
This commit is contained in:
@@ -7,7 +7,7 @@ export async function up(knex: Knex) {
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_presets', (table) => {
|
||||
await knex.schema.alterTable('directus_files', (table) => {
|
||||
table.integer('filesize').notNullable().defaultTo(0).alter();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_collections', (table) => {
|
||||
table.string('accountability').defaultTo('all');
|
||||
});
|
||||
|
||||
await knex('directus_collections').update({ accountability: 'all' });
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_collections', (table) => {
|
||||
table.dropColumn('accountability');
|
||||
});
|
||||
}
|
||||
@@ -21,10 +21,10 @@ data:
|
||||
- collection: directus_files
|
||||
icon: folder
|
||||
note: Metadata for all managed file assets
|
||||
display_template: "{{ title }}"
|
||||
display_template: '{{ title }}'
|
||||
- collection: directus_folders
|
||||
note: Provides virtual directories for files
|
||||
display_template: "{{ name }}"
|
||||
display_template: '{{ name }}'
|
||||
- collection: directus_migrations
|
||||
note: What version of the database you're using
|
||||
- collection: directus_permissions
|
||||
@@ -33,6 +33,7 @@ data:
|
||||
- collection: directus_presets
|
||||
icon: bookmark_border
|
||||
note: Presets for collection defaults and bookmarks
|
||||
accountability: null
|
||||
- collection: directus_relations
|
||||
icon: merge_type
|
||||
note: Relationship configuration and metadata
|
||||
@@ -52,6 +53,6 @@ data:
|
||||
unarchive_value: draft
|
||||
icon: people_alt
|
||||
note: System users for the platform
|
||||
display_template: "{{ first_name }} {{ last_name }}"
|
||||
display_template: '{{ first_name }} {{ last_name }}'
|
||||
- collection: directus_webhooks
|
||||
note: Configuration for event-based HTTP requests
|
||||
|
||||
@@ -135,3 +135,25 @@ fields:
|
||||
- integer
|
||||
allowNone: true
|
||||
width: half
|
||||
|
||||
- field: accountability_divider
|
||||
special:
|
||||
- alias
|
||||
- no-data
|
||||
interface: divider
|
||||
options:
|
||||
icon: admin_panel_settings
|
||||
title: Accountability
|
||||
width: full
|
||||
|
||||
- field: accountability
|
||||
interface: dropdown
|
||||
options:
|
||||
choices:
|
||||
- text: '$t:field_options.directus_collections.track_activity_revisions'
|
||||
value: all
|
||||
- text: '$t:field_options.directus_collections.only_track_activity'
|
||||
value: activity
|
||||
- text: '$t:field_options.directus_collections.do_not_track_anything'
|
||||
value: null
|
||||
width: half
|
||||
|
||||
@@ -129,7 +129,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
await payloadService.processO2M(payloads, key);
|
||||
}
|
||||
|
||||
if (this.accountability) {
|
||||
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
||||
const activityRecords = primaryKeys.map((key) => ({
|
||||
action: Action.CREATE,
|
||||
user: this.accountability!.user,
|
||||
@@ -153,16 +153,18 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
activityPrimaryKeys.push(primaryKey);
|
||||
}
|
||||
|
||||
const revisionRecords = activityPrimaryKeys.map((key, index) => ({
|
||||
activity: key,
|
||||
collection: this.collection,
|
||||
item: primaryKeys[index],
|
||||
data: JSON.stringify(payloads[index]),
|
||||
delta: JSON.stringify(payloads[index]),
|
||||
}));
|
||||
if (this.schema.collections[this.collection].accountability === 'all') {
|
||||
const revisionRecords = activityPrimaryKeys.map((key, index) => ({
|
||||
activity: key,
|
||||
collection: this.collection,
|
||||
item: primaryKeys[index],
|
||||
data: JSON.stringify(payloads[index]),
|
||||
delta: JSON.stringify(payloads[index]),
|
||||
}));
|
||||
|
||||
if (revisionRecords.length > 0) {
|
||||
await trx.insert(revisionRecords).into('directus_revisions');
|
||||
if (revisionRecords.length > 0) {
|
||||
await trx.insert(revisionRecords).into('directus_revisions');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +337,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
await payloadService.processO2M(payload, key);
|
||||
}
|
||||
|
||||
if (this.accountability) {
|
||||
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
||||
const activityRecords = keys.map((key) => ({
|
||||
action: Action.UPDATE,
|
||||
user: this.accountability!.user,
|
||||
@@ -357,24 +359,26 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
activityPrimaryKeys.push(primaryKey);
|
||||
}
|
||||
|
||||
const itemsService = new ItemsService(this.collection, {
|
||||
knex: trx,
|
||||
schema: this.schema,
|
||||
});
|
||||
if (this.schema.collections[this.collection].accountability === 'all') {
|
||||
const itemsService = new ItemsService(this.collection, {
|
||||
knex: trx,
|
||||
schema: this.schema,
|
||||
});
|
||||
|
||||
const snapshots = await itemsService.readByKey(keys);
|
||||
const snapshots = await itemsService.readByKey(keys);
|
||||
|
||||
const revisionRecords = activityPrimaryKeys.map((key, index) => ({
|
||||
activity: key,
|
||||
collection: this.collection,
|
||||
item: keys[index],
|
||||
data:
|
||||
snapshots && Array.isArray(snapshots) ? JSON.stringify(snapshots?.[index]) : JSON.stringify(snapshots),
|
||||
delta: JSON.stringify(payloadWithoutAliasAndPK),
|
||||
}));
|
||||
const revisionRecords = activityPrimaryKeys.map((key, index) => ({
|
||||
activity: key,
|
||||
collection: this.collection,
|
||||
item: keys[index],
|
||||
data:
|
||||
snapshots && Array.isArray(snapshots) ? JSON.stringify(snapshots?.[index]) : JSON.stringify(snapshots),
|
||||
delta: JSON.stringify(payloadWithoutAliasAndPK),
|
||||
}));
|
||||
|
||||
if (revisionRecords.length > 0) {
|
||||
await trx.insert(revisionRecords).into('directus_revisions');
|
||||
if (revisionRecords.length > 0) {
|
||||
await trx.insert(revisionRecords).into('directus_revisions');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -502,7 +506,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
await this.knex.transaction(async (trx) => {
|
||||
await trx(this.collection).whereIn(primaryKeyField, keys).delete();
|
||||
|
||||
if (this.accountability) {
|
||||
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
||||
const activityRecords = keys.map((key) => ({
|
||||
action: Action.DELETE,
|
||||
user: this.accountability!.user,
|
||||
|
||||
@@ -8,6 +8,7 @@ export type CollectionMeta = {
|
||||
singleton: boolean;
|
||||
icon: string | null;
|
||||
translations: Record<string, string>;
|
||||
accountability: 'all' | 'accountability' | null;
|
||||
};
|
||||
|
||||
export type Collection = {
|
||||
|
||||
@@ -9,6 +9,7 @@ type CollectionsOverview = {
|
||||
singleton: boolean;
|
||||
sortField: string | null;
|
||||
note: string | null;
|
||||
accountability: 'all' | 'activity' | null;
|
||||
fields: {
|
||||
[name: string]: {
|
||||
field: string;
|
||||
|
||||
@@ -68,7 +68,9 @@ export async function getSchema(options?: {
|
||||
const schemaOverview = await schemaInspector.overview();
|
||||
|
||||
const collections = [
|
||||
...(await database.select('collection', 'singleton', 'note', 'sort_field').from('directus_collections')),
|
||||
...(await database
|
||||
.select('collection', 'singleton', 'note', 'sort_field', 'accountability')
|
||||
.from('directus_collections')),
|
||||
...systemCollectionRows,
|
||||
];
|
||||
|
||||
@@ -87,6 +89,7 @@ export async function getSchema(options?: {
|
||||
collectionMeta?.singleton === true || collectionMeta?.singleton === 'true' || collectionMeta?.singleton === 1,
|
||||
note: collectionMeta?.note || null,
|
||||
sortField: collectionMeta?.sort_field || null,
|
||||
accountability: collectionMeta ? collectionMeta.accountability : 'all',
|
||||
fields: mapValues(schemaOverview[collection].columns, (column) => ({
|
||||
field: column.column_name,
|
||||
defaultValue: getDefaultValue(column) ?? null,
|
||||
|
||||
@@ -50,5 +50,11 @@ export function useCollection(collectionKey: string | Ref<string>) {
|
||||
return info.value?.meta?.singleton === true;
|
||||
});
|
||||
|
||||
return { info, fields, defaults, primaryKeyField, userCreatedField, sortField, isSingleton };
|
||||
const accountabilityScope = computed(() => {
|
||||
if (!info.value) return null;
|
||||
if (!info.value.meta) return null;
|
||||
return info.value.meta.accountability;
|
||||
});
|
||||
|
||||
return { info, fields, defaults, primaryKeyField, userCreatedField, sortField, isSingleton, accountabilityScope };
|
||||
}
|
||||
|
||||
@@ -689,6 +689,7 @@ fields:
|
||||
archive_value: Archive Value
|
||||
unarchive_value: Unarchive Value
|
||||
sort_field: Sort Field
|
||||
accountability: Activity & Revision Tracking
|
||||
directus_files:
|
||||
title: Title
|
||||
description: Description
|
||||
@@ -762,6 +763,11 @@ fields:
|
||||
users: Users in Role
|
||||
module_list: Module Navigation
|
||||
collection_list: Collection Navigation
|
||||
field_options:
|
||||
directus_collections:
|
||||
track_activity_revisions: Track Activity & Revisions
|
||||
only_track_activity: Only Track Activity
|
||||
do_not_track_anything: Do Not Track Anything
|
||||
no_fields_in_collection: 'There are no fields in "{collection}" yet'
|
||||
do_nothing: Do Nothing
|
||||
generate_and_save_uuid: Generate and Save UUID
|
||||
|
||||
@@ -175,9 +175,10 @@
|
||||
<div class="page-description" v-html="marked($t('page_help_collections_item'))" />
|
||||
</sidebar-detail>
|
||||
<revisions-drawer-detail
|
||||
v-if="isNew === false && _primaryKey && revisionsAllowed"
|
||||
v-if="isNew === false && _primaryKey && revisionsAllowed && accountabilityScope === 'all'"
|
||||
:collection="collection"
|
||||
:primary-key="_primaryKey"
|
||||
:scope="accountabilityScope"
|
||||
ref="revisionsDrawerDetail"
|
||||
@revert="revert"
|
||||
/>
|
||||
@@ -191,7 +192,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-api';
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import Vue from 'vue';
|
||||
|
||||
import CollectionsNavigationSearch from '../components/navigation-search.vue';
|
||||
@@ -245,7 +246,9 @@ export default defineComponent({
|
||||
|
||||
const revisionsDrawerDetail = ref<Vue | null>(null);
|
||||
|
||||
const { info: collectionInfo, defaults, primaryKeyField, isSingleton } = useCollection(collection);
|
||||
const { info: collectionInfo, defaults, primaryKeyField, isSingleton, accountabilityScope } = useCollection(
|
||||
collection
|
||||
);
|
||||
|
||||
const {
|
||||
isNew,
|
||||
@@ -396,6 +399,7 @@ export default defineComponent({
|
||||
_primaryKey,
|
||||
revisionsAllowed,
|
||||
revert,
|
||||
accountabilityScope,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface CollectionRaw {
|
||||
archive_value: string | null;
|
||||
unarchive_value: string | null;
|
||||
archive_app_filter: boolean;
|
||||
accountability: 'all' | 'activity' | null;
|
||||
} | null;
|
||||
schema: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props) {
|
||||
const { revisions, revisionsByDate, loading, refresh, revisionsCount, created } = useRevisions(
|
||||
props.collection,
|
||||
props.primaryKey
|
||||
|
||||
Reference in New Issue
Block a user