Skip to content

Commit 747d373

Browse files
committed
Feature #710: View deleted Entities and restore Entities
1 parent 454ade2 commit 747d373

File tree

22 files changed

+888
-93
lines changed

22 files changed

+888
-93
lines changed

src/components/dataset/entities.vue

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ including this file, may be copied, modified, propagated, or distributed
1010
except according to the terms contained in the LICENSE file.
1111
-->
1212
<template>
13-
<div>
13+
<div id="dataset-entities">
1414
<page-section>
1515
<template #heading>
1616
<span>{{ $t('resource.entities') }}</span>
@@ -19,10 +19,22 @@ except according to the terms contained in the LICENSE file.
1919
class="btn btn-primary" @click="upload.show()">
2020
<span class="icon-upload"></span>{{ $t('action.upload') }}
2121
</button>
22-
<odata-data-access @analyze="analyze.show()"/>
22+
<template v-if="deletedEntityCount.dataExists">
23+
<button v-if="canDelete && (deletedEntityCount.value > 0 || deleted)" type="button"
24+
class="btn toggle-deleted-entities" :class="{ 'btn-danger': deleted, 'btn-link': !deleted }"
25+
@click="toggleDeleted">
26+
<span class="icon-trash"></span>{{ $tcn('action.toggleDeletedEntities', deletedEntityCount.value) }}
27+
<span v-show="deleted" class="icon-close"></span>
28+
</button>
29+
</template>
30+
<odata-data-access :analyze-disabled="deleted"
31+
:analyze-disabled-message="$t('analyzeDisabledDeletedData')"
32+
@analyze="analyze.show()"/>
2333
</template>
2434
<template #body>
25-
<entity-list ref="list" :project-id="projectId" :dataset-name="datasetName"/>
35+
<entity-list ref="list" :project-id="projectId"
36+
:dataset-name="datasetName" :deleted="deleted"
37+
@fetch-deleted-count="fetchDeletedCount"/>
2638
</template>
2739
</page-section>
2840

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

3547
<script>
36-
import { defineAsyncComponent } from 'vue';
48+
import { defineAsyncComponent, watchEffect, computed } from 'vue';
49+
import { useRouter } from 'vue-router';
3750
3851
import EntityList from '../entity/list.vue';
3952
import OdataAnalyze from '../odata/analyze.vue';
4053
import OdataDataAccess from '../odata/data-access.vue';
4154
import PageSection from '../page/section.vue';
55+
import useEntities from '../../request-data/entities';
56+
import useQueryRef from '../../composables/query-ref';
4257
4358
import { apiPaths } from '../../util/request';
4459
import { loadAsync } from '../../util/load-async';
4560
import { modalData } from '../../util/reactivity';
4661
import { useRequestData } from '../../request-data';
62+
import { noop } from '../../util/util';
4763
4864
export default {
4965
name: 'DatasetEntities',
@@ -67,7 +83,28 @@ export default {
6783
},
6884
setup() {
6985
const { project, dataset } = useRequestData();
70-
return { project, dataset };
86+
const { deletedEntityCount } = useEntities();
87+
const router = useRouter();
88+
89+
const deleted = useQueryRef({
90+
fromQuery: (query) => {
91+
if (typeof query.deleted === 'string' && query.deleted === 'true') {
92+
return true;
93+
}
94+
return false;
95+
},
96+
toQuery: (value) => ({
97+
deleted: value === true ? 'true' : null
98+
})
99+
});
100+
101+
const canDelete = computed(() => project.dataExists && project.permits('entity.delete'));
102+
103+
watchEffect(() => {
104+
if (deleted.value && project.dataExists && !canDelete.value) router.push('/');
105+
});
106+
107+
return { project, dataset, deletedEntityCount, deleted, canDelete };
71108
},
72109
data() {
73110
return {
@@ -81,6 +118,9 @@ export default {
81118
return `${window.location.origin}${path}`;
82119
}
83120
},
121+
created() {
122+
if (!this.deleted) this.fetchDeletedCount();
123+
},
84124
methods: {
85125
afterUpload(count) {
86126
this.upload.hide();
@@ -89,21 +129,66 @@ export default {
89129
// Update dataset.entities so that the count in the OData loading message
90130
// reflects the new entities.
91131
this.dataset.entities += count;
132+
},
133+
fetchDeletedCount() {
134+
this.deletedEntityCount.request({
135+
method: 'GET',
136+
url: apiPaths.odataEntities(
137+
this.projectId,
138+
this.datasetName,
139+
{
140+
$top: 0,
141+
$count: true,
142+
$filter: '__system/deletedAt ne null',
143+
}
144+
),
145+
}).catch(noop);
146+
},
147+
toggleDeleted() {
148+
const { path } = this.$route;
149+
this.$router.push(this.deleted ? path : `${path}?deleted=true`);
92150
}
93151
}
94152
};
95153
</script>
96154

97155
<style lang="scss">
156+
@import '../../assets/scss/variables';
157+
98158
#odata-data-access { float: right; }
159+
160+
#dataset-entities {
161+
.toggle-deleted-entities {
162+
margin-left: 8px;
163+
164+
&.btn-link {
165+
color: $color-danger;
166+
}
167+
168+
.icon-close { margin-left: 3px; }
169+
}
170+
171+
.purge-description {
172+
display: inline;
173+
position: relative;
174+
top: -5px;
175+
left: 12px;
176+
font-size: 14px;
177+
}
178+
}
99179
</style>
100180

101181
<i18n lang="json5">
102182
{
103183
"en": {
104184
"alert": {
105185
"upload": "Success! Your Entities have been uploaded."
106-
}
186+
},
187+
"purgeDescription": "Entities are deleted after 30 days in the Trash",
188+
"action": {
189+
"toggleDeletedEntities": "{count} deleted Entity | {count} deleted Entities"
190+
},
191+
"analyzeDisabledDeletedData": "OData access is unavailable for deleted Entities",
107192
}
108193
}
109194
</i18n>

src/components/entity/delete.vue

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ except according to the terms contained in the LICENSE file.
1212
<template>
1313
<modal id="entity-delete" :state="state" :hideable="!awaitingResponse"
1414
backdrop @hide="$emit('hide')" @shown="focusCheckbox">
15-
<template #title>{{ $t('title', { label }) }}</template>
15+
<template #title>{{ $t('title', { label: entity?.label }) }}</template>
1616
<template #body>
1717
<p class="modal-introduction">
18-
<span>{{ $t('introduction[0]', { label }) }}</span>
18+
<span>{{ $t('introduction[0]', { label: entity?.label }) }}</span>
1919
<sentence-separator/>
2020
<span>{{ $t('common.noUndo') }}</span>
2121
</p>
@@ -54,10 +54,7 @@ defineOptions({
5454
const props = defineProps({
5555
state: Boolean,
5656
checkbox: Boolean,
57-
label: {
58-
type: String,
59-
default: ''
60-
},
57+
entity: Object,
6158
awaitingResponse: Boolean
6259
});
6360
const emit = defineEmits(['hide', 'delete']);
@@ -70,9 +67,9 @@ const focusCheckbox = () => { if (props.checkbox) input.value.focus(); };
7067
7168
const del = () => {
7269
if (props.checkbox)
73-
emit('delete', !noConfirm.value);
70+
emit('delete', [props.entity, !noConfirm.value]);
7471
else
75-
emit('delete');
72+
emit('delete', [props.entity]);
7673
};
7774
</script>
7875

src/components/entity/filters.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ except according to the terms contained in the LICENSE file.
1414
<div class="form-group">
1515
<span class="icon-filter"></span><span>{{ $t('common.filter') }}</span>
1616
</div>
17-
<entity-filters-conflict :model-value="conflict"
18-
@update:model-value="$emit('update:conflict', $event)"/>
17+
<entity-filters-conflict :model-value="conflict" :disabled="disabled"
18+
:disabled-message="disabledMessage" @update:model-value="$emit('update:conflict', $event)"/>
1919
</span>
2020
</template>
2121

@@ -29,6 +29,14 @@ defineProps({
2929
conflict: {
3030
type: Array,
3131
required: true
32+
},
33+
disabled: {
34+
type: Boolean,
35+
required: true
36+
},
37+
disabledMessage: {
38+
type: String,
39+
required: false
3240
}
3341
});
3442
defineEmits(['update:conflict']);

0 commit comments

Comments
 (0)