Skip to content

Commit

Permalink
Feature #710: View deleted Entities and restore Entities
Browse files Browse the repository at this point in the history
  • Loading branch information
sadiqkhoja committed Dec 31, 2024
1 parent 454ade2 commit 747d373
Show file tree
Hide file tree
Showing 22 changed files with 888 additions and 93 deletions.
97 changes: 91 additions & 6 deletions src/components/dataset/entities.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<div>
<div id="dataset-entities">
<page-section>
<template #heading>
<span>{{ $t('resource.entities') }}</span>
Expand All @@ -19,10 +19,22 @@ except according to the terms contained in the LICENSE file.
class="btn btn-primary" @click="upload.show()">
<span class="icon-upload"></span>{{ $t('action.upload') }}
</button>
<odata-data-access @analyze="analyze.show()"/>
<template v-if="deletedEntityCount.dataExists">
<button v-if="canDelete && (deletedEntityCount.value > 0 || deleted)" type="button"
class="btn toggle-deleted-entities" :class="{ 'btn-danger': deleted, 'btn-link': !deleted }"
@click="toggleDeleted">
<span class="icon-trash"></span>{{ $tcn('action.toggleDeletedEntities', deletedEntityCount.value) }}
<span v-show="deleted" class="icon-close"></span>
</button>
</template>
<odata-data-access :analyze-disabled="deleted"
:analyze-disabled-message="$t('analyzeDisabledDeletedData')"
@analyze="analyze.show()"/>
</template>
<template #body>
<entity-list ref="list" :project-id="projectId" :dataset-name="datasetName"/>
<entity-list ref="list" :project-id="projectId"
:dataset-name="datasetName" :deleted="deleted"
@fetch-deleted-count="fetchDeletedCount"/>
</template>
</page-section>

Expand All @@ -33,17 +45,21 @@ except according to the terms contained in the LICENSE file.
</template>

<script>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, watchEffect, computed } from 'vue';
import { useRouter } from 'vue-router';

import EntityList from '../entity/list.vue';
import OdataAnalyze from '../odata/analyze.vue';
import OdataDataAccess from '../odata/data-access.vue';
import PageSection from '../page/section.vue';
import useEntities from '../../request-data/entities';
import useQueryRef from '../../composables/query-ref';

import { apiPaths } from '../../util/request';
import { loadAsync } from '../../util/load-async';
import { modalData } from '../../util/reactivity';
import { useRequestData } from '../../request-data';
import { noop } from '../../util/util';

export default {
name: 'DatasetEntities',
Expand All @@ -67,7 +83,28 @@ export default {
},
setup() {
const { project, dataset } = useRequestData();
return { project, dataset };
const { deletedEntityCount } = useEntities();
const router = useRouter();

const deleted = useQueryRef({
fromQuery: (query) => {
if (typeof query.deleted === 'string' && query.deleted === 'true') {
return true;
}
return false;
},
toQuery: (value) => ({
deleted: value === true ? 'true' : null
})
});

const canDelete = computed(() => project.dataExists && project.permits('entity.delete'));

watchEffect(() => {
if (deleted.value && project.dataExists && !canDelete.value) router.push('/');
});

return { project, dataset, deletedEntityCount, deleted, canDelete };
},
data() {
return {
Expand All @@ -81,6 +118,9 @@ export default {
return `${window.location.origin}${path}`;
}
},
created() {
if (!this.deleted) this.fetchDeletedCount();
},
methods: {
afterUpload(count) {
this.upload.hide();
Expand All @@ -89,21 +129,66 @@ export default {
// Update dataset.entities so that the count in the OData loading message
// reflects the new entities.
this.dataset.entities += count;
},
fetchDeletedCount() {
this.deletedEntityCount.request({
method: 'GET',
url: apiPaths.odataEntities(
this.projectId,
this.datasetName,
{
$top: 0,
$count: true,
$filter: '__system/deletedAt ne null',
}
),
}).catch(noop);
},
toggleDeleted() {
const { path } = this.$route;
this.$router.push(this.deleted ? path : `${path}?deleted=true`);
}
}
};
</script>

<style lang="scss">
@import '../../assets/scss/variables';

#odata-data-access { float: right; }

#dataset-entities {
.toggle-deleted-entities {
margin-left: 8px;

&.btn-link {
color: $color-danger;
}

.icon-close { margin-left: 3px; }
}

.purge-description {
display: inline;
position: relative;
top: -5px;
left: 12px;
font-size: 14px;
}
}
</style>

<i18n lang="json5">
{
"en": {
"alert": {
"upload": "Success! Your Entities have been uploaded."
}
},
"purgeDescription": "Entities are deleted after 30 days in the Trash",
"action": {
"toggleDeletedEntities": "{count} deleted Entity | {count} deleted Entities"
},
"analyzeDisabledDeletedData": "OData access is unavailable for deleted Entities",
}
}
</i18n>
Expand Down
13 changes: 5 additions & 8 deletions src/components/entity/delete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ except according to the terms contained in the LICENSE file.
<template>
<modal id="entity-delete" :state="state" :hideable="!awaitingResponse"
backdrop @hide="$emit('hide')" @shown="focusCheckbox">
<template #title>{{ $t('title', { label }) }}</template>
<template #title>{{ $t('title', { label: entity?.label }) }}</template>
<template #body>
<p class="modal-introduction">
<span>{{ $t('introduction[0]', { label }) }}</span>
<span>{{ $t('introduction[0]', { label: entity?.label }) }}</span>
<sentence-separator/>
<span>{{ $t('common.noUndo') }}</span>
</p>
Expand Down Expand Up @@ -54,10 +54,7 @@ defineOptions({
const props = defineProps({
state: Boolean,
checkbox: Boolean,
label: {
type: String,
default: ''
},
entity: Object,
awaitingResponse: Boolean
});
const emit = defineEmits(['hide', 'delete']);
Expand All @@ -70,9 +67,9 @@ const focusCheckbox = () => { if (props.checkbox) input.value.focus(); };

const del = () => {
if (props.checkbox)
emit('delete', !noConfirm.value);
emit('delete', [props.entity, !noConfirm.value]);
else
emit('delete');
emit('delete', [props.entity]);
};
</script>

Expand Down
12 changes: 10 additions & 2 deletions src/components/entity/filters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ except according to the terms contained in the LICENSE file.
<div class="form-group">
<span class="icon-filter"></span><span>{{ $t('common.filter') }}</span>
</div>
<entity-filters-conflict :model-value="conflict"
@update:model-value="$emit('update:conflict', $event)"/>
<entity-filters-conflict :model-value="conflict" :disabled="disabled"
:disabled-message="disabledMessage" @update:model-value="$emit('update:conflict', $event)"/>
</span>
</template>

Expand All @@ -29,6 +29,14 @@ defineProps({
conflict: {
type: Array,
required: true
},
disabled: {
type: Boolean,
required: true
},
disabledMessage: {
type: String,
required: false
}
});
defineEmits(['update:conflict']);
Expand Down
Loading

0 comments on commit 747d373

Please sign in to comment.