mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-28 02:58:18 +08:00
Merge pull request #17318 from Renni771/port-history-panel-to-composition-api-and-typescript
Port HistoryPanel component to composition API and typescript
This commit is contained in:
@@ -1,8 +1,361 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { GetComponentPropTypes } from "types/utilityTypes";
|
||||
import { computed, onMounted, PropType, ref, set as VueSet, unref, watch } from "vue";
|
||||
|
||||
import { copyDataset } from "@/api/datasets";
|
||||
import ExpandedItems from "@/components/History/Content/ExpandedItems";
|
||||
import SelectedItems from "@/components/History/Content/SelectedItems";
|
||||
import { HistoryFilters } from "@/components/History/HistoryFilters";
|
||||
import { deleteContent, updateContentFields } from "@/components/History/model/queries";
|
||||
import { Toast } from "@/composables/toast";
|
||||
import { rewatchHistory } from "@/store/historyStore/model/watchHistory";
|
||||
import { type HistoryItem, useHistoryItemsStore } from "@/stores/historyItemsStore";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import { type Alias, getOperatorForAlias } from "@/utils/filtering";
|
||||
|
||||
import HistoryCounter from "./HistoryCounter.vue";
|
||||
import HistoryDetails from "./HistoryDetails.vue";
|
||||
import HistoryDropZone from "./HistoryDropZone.vue";
|
||||
import HistoryEmpty from "./HistoryEmpty.vue";
|
||||
import HistoryMessages from "./HistoryMessages.vue";
|
||||
import HistoryOperations from "./HistoryOperations/HistoryOperations.vue";
|
||||
import OperationErrorDialog from "./HistoryOperations/OperationErrorDialog.vue";
|
||||
import SelectionChangeWarning from "./HistoryOperations/SelectionChangeWarning.vue";
|
||||
import HistorySelectionOperations from "./HistoryOperations/SelectionOperations.vue";
|
||||
import HistorySelectionStatus from "./HistoryOperations/SelectionStatus.vue";
|
||||
import FilterMenu from "@/components/Common/FilterMenu.vue";
|
||||
import ContentItem from "@/components/History/Content/ContentItem.vue";
|
||||
import ListingLayout from "@/components/History/Layout/ListingLayout.vue";
|
||||
import LoadingSpan from "@/components/LoadingSpan.vue";
|
||||
|
||||
interface BackendFilterError {
|
||||
err_msg: string;
|
||||
err_code: number;
|
||||
column?: string;
|
||||
col?: string;
|
||||
operation?: Alias;
|
||||
op?: Alias;
|
||||
value?: string;
|
||||
val?: string;
|
||||
ValueError?: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
listOffset: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
history: {
|
||||
type: Object as PropType<Record<string, any> & GetComponentPropTypes<typeof HistoryCounter>["history"]>,
|
||||
required: true,
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
canEditHistory: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
shouldShowControls: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const filterClass = HistoryFilters;
|
||||
const filterText = ref("");
|
||||
const invisibleHistoryItems = ref<Record<string, any>>({});
|
||||
const isLoading = ref(false);
|
||||
const offsetQueryParam = ref(0);
|
||||
const searchError = ref<BackendFilterError | undefined>(undefined);
|
||||
const showAdvanced = ref(false);
|
||||
const showDropZone = ref(false);
|
||||
const operationRunning = ref(null);
|
||||
const operationError = ref(null);
|
||||
const querySelectionBreak = ref(false);
|
||||
const dragTarget = ref<EventTarget | null>(null);
|
||||
|
||||
const { currentFilterText, currentHistoryId } = storeToRefs(useHistoryStore());
|
||||
const { lastCheckedTime, totalMatchesCount, isWatching } = storeToRefs(useHistoryItemsStore());
|
||||
|
||||
const historyStore = useHistoryStore();
|
||||
const historyItemsStore = useHistoryItemsStore();
|
||||
|
||||
const historyUpdateTime = computed(() => {
|
||||
return props.history.update_time as Date;
|
||||
});
|
||||
|
||||
const queryKey = computed(() => {
|
||||
return `${props.history.id}-${filterText.value}`;
|
||||
});
|
||||
|
||||
const queryDefault = computed(() => {
|
||||
return !filterText.value;
|
||||
});
|
||||
|
||||
const isProcessing = computed(() => {
|
||||
if (!operationRunning.value) {
|
||||
return false;
|
||||
}
|
||||
return operationRunning?.value >= props.history.update_time;
|
||||
});
|
||||
|
||||
const historyItems = computed(() => {
|
||||
return historyItemsStore.getHistoryItems(props.history.id, filterText.value);
|
||||
});
|
||||
|
||||
const formattedSearchError = computed(() => {
|
||||
const newError = unref(searchError);
|
||||
if (!newError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { column, col, operation, op, value, val, err_msg, ValueError } = newError;
|
||||
const alias = operation || op;
|
||||
const operator = alias ? getOperatorForAlias(alias) : ":";
|
||||
|
||||
return {
|
||||
filter: `${column || col}${operator}`,
|
||||
value: value || val,
|
||||
msg: err_msg,
|
||||
typeError: ValueError,
|
||||
};
|
||||
});
|
||||
|
||||
const storeFilterText = computed(() => {
|
||||
if (props.history.id !== currentHistoryId.value) {
|
||||
return "";
|
||||
}
|
||||
return currentFilterText.value || "";
|
||||
});
|
||||
|
||||
watch(offsetQueryParam, () => loadHistoryItems());
|
||||
|
||||
watch(
|
||||
() => queryKey.value,
|
||||
() => {
|
||||
invisibleHistoryItems.value = {};
|
||||
offsetQueryParam.value = 0;
|
||||
loadHistoryItems();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.filter,
|
||||
(newVal) => (filterText.value = newVal)
|
||||
);
|
||||
|
||||
watch(filterText, (newFilterTextValue) => {
|
||||
if (props.filterable) {
|
||||
historyStore.setFilterText(props.history.id, newFilterTextValue);
|
||||
}
|
||||
});
|
||||
|
||||
watch(storeFilterText, (newFilterTextValue) => {
|
||||
if (props.filterable) {
|
||||
filterText.value = newFilterTextValue;
|
||||
}
|
||||
});
|
||||
|
||||
watch(historyUpdateTime, async () => await loadHistoryItems());
|
||||
|
||||
watch(
|
||||
() => props.history.id,
|
||||
(newValue, currentValue) => {
|
||||
if (newValue !== currentValue) {
|
||||
operationRunning.value = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(historyItems, (newHistoryItems) => {
|
||||
if (!invisibleHistoryItems.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-hide invisible history items when `historyItems` changes
|
||||
for (const newHistoryItem of newHistoryItems) {
|
||||
if (invisibleHistoryItems.value[newHistoryItem.hid]) {
|
||||
VueSet(invisibleHistoryItems.value, newHistoryItem.hid, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getHighlight(item: HistoryItem) {
|
||||
if (unref(isLoading)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const highlightsKey = parseInt(filterClass.getFilterValue(unref(filterText), "related"));
|
||||
if (!highlightsKey) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (highlightsKey === item.hid) {
|
||||
return "active";
|
||||
}
|
||||
|
||||
if (item.hid > highlightsKey) {
|
||||
return "output";
|
||||
}
|
||||
|
||||
return "input";
|
||||
}
|
||||
|
||||
function hasMatches(items: HistoryItem[]) {
|
||||
return !!items && items.length > 0;
|
||||
}
|
||||
|
||||
function isDataset(item: HistoryItem) {
|
||||
return item.history_content_type === "dataset";
|
||||
}
|
||||
|
||||
async function loadHistoryItems() {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await historyItemsStore.fetchHistoryItems(props.history.id, filterText.value, offsetQueryParam.value);
|
||||
searchError.value = undefined;
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.data && error.response.data.err_msg) {
|
||||
console.debug("HistoryPanel - Load items error:", error.response.data.err_msg);
|
||||
searchError.value = error.response.data;
|
||||
} else {
|
||||
console.debug("HistoryPanel - Load items error.", error);
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete(item: HistoryItem, recursive = false) {
|
||||
isLoading.value = true;
|
||||
setInvisible(item);
|
||||
|
||||
try {
|
||||
await deleteContent(item, { recursive: recursive });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onHideSelection(selectedItems: HistoryItem[]) {
|
||||
for (const item of selectedItems) {
|
||||
setInvisible(item);
|
||||
}
|
||||
}
|
||||
|
||||
function onScroll(newOffset: number) {
|
||||
offsetQueryParam.value = newOffset;
|
||||
}
|
||||
|
||||
async function onUndelete(item: HistoryItem) {
|
||||
setInvisible(item);
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await updateContentFields(item, { deleted: false });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onUnhide(item: HistoryItem) {
|
||||
setInvisible(item);
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await updateContentFields(item, { visible: true });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reloadContents() {
|
||||
rewatchHistory();
|
||||
}
|
||||
|
||||
function setInvisible(item: HistoryItem) {
|
||||
VueSet(unref(invisibleHistoryItems), item.hid, true);
|
||||
}
|
||||
|
||||
function onTagChange(item: HistoryItem, newTags: string[]) {
|
||||
item.tags = newTags;
|
||||
}
|
||||
|
||||
function onOperationError(error: any) {
|
||||
console.debug("HistoryPanel - Operation error.", error);
|
||||
operationError.value = error;
|
||||
}
|
||||
|
||||
function onDragEnter(e: DragEvent) {
|
||||
dragTarget.value = e.target;
|
||||
showDropZone.value = true;
|
||||
}
|
||||
|
||||
function onDragLeave(e: DragEvent) {
|
||||
if (dragTarget.value === e.target) {
|
||||
showDropZone.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onDrop(evt: any) {
|
||||
showDropZone.value = false;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(evt.dataTransfer.getData("text"))[0];
|
||||
} catch (error) {
|
||||
// this was not a valid object for this dropzone, ignore
|
||||
}
|
||||
|
||||
if (!data || data.history_id === props.history.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const dataSource = data.history_content_type === "dataset" ? "hda" : "hdca";
|
||||
await copyDataset(data.id, props.history.id, data.history_content_type, dataSource);
|
||||
|
||||
if (data.history_content_type === "dataset") {
|
||||
Toast.info("Dataset copied to history");
|
||||
} else {
|
||||
Toast.info("Collection copied to history");
|
||||
}
|
||||
|
||||
historyStore.loadHistoryById(props.history.id);
|
||||
} catch (error) {
|
||||
Toast.error(`${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilterValue(newFilterText: string, newValue: any) {
|
||||
const currentFilterText = filterText.value;
|
||||
filterText.value = filterClass.setFilterValue(currentFilterText, newFilterText, newValue);
|
||||
}
|
||||
|
||||
function getItemKey(item: HistoryItem) {
|
||||
return item.type_id;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// `filterable` here indicates if this is the current history panel
|
||||
if (props.filterable && !props.filter) {
|
||||
filterText.value = storeFilterText.value;
|
||||
}
|
||||
await loadHistoryItems();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ExpandedItems
|
||||
v-slot="{ expandedCount, isExpanded, setExpanded, collapseAll }"
|
||||
:scope-key="historyId"
|
||||
:get-item-key="(item) => item.type_id">
|
||||
:scope-key="props.history.id"
|
||||
:get-item-key="getItemKey">
|
||||
<SelectedItems
|
||||
v-slot="{
|
||||
selectedItems,
|
||||
@@ -16,10 +369,11 @@
|
||||
resetSelection,
|
||||
}"
|
||||
:scope-key="queryKey"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
:get-item-key="getItemKey"
|
||||
:filter-text="filterText"
|
||||
:total-items-in-query="totalMatchesCount"
|
||||
@query-selection-break="querySelectionBreak = true">
|
||||
<!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->
|
||||
<section
|
||||
class="history-layout d-flex flex-column w-100"
|
||||
@drop.prevent="onDrop"
|
||||
@@ -27,6 +381,7 @@
|
||||
@dragover.prevent
|
||||
@dragleave.prevent="onDragLeave">
|
||||
<slot name="navigation" :history="history" />
|
||||
|
||||
<FilterMenu
|
||||
v-if="filterable"
|
||||
class="content-operations-filters mx-3"
|
||||
@@ -34,25 +389,32 @@
|
||||
placeholder="search datasets"
|
||||
:filter-class="filterClass"
|
||||
:filter-text.sync="filterText"
|
||||
:loading="loading"
|
||||
:loading="isLoading"
|
||||
:search-error="searchError"
|
||||
:show-advanced.sync="showAdvanced" />
|
||||
|
||||
<section v-if="!showAdvanced">
|
||||
<HistoryDetails :history="history" :writeable="writable" @update:history="updateHistory($event)" />
|
||||
<HistoryDetails
|
||||
:history="history"
|
||||
:writeable="canEditHistory"
|
||||
@update:history="historyStore.updateHistory($event)" />
|
||||
|
||||
<HistoryMessages :history="history" />
|
||||
|
||||
<HistoryCounter
|
||||
:history="history"
|
||||
:is-watching="isWatching"
|
||||
:last-checked="lastCheckedTime"
|
||||
:show-controls="showControls"
|
||||
:show-controls="shouldShowControls"
|
||||
:filter-text.sync="filterText"
|
||||
@reloadContents="reloadContents" />
|
||||
|
||||
<HistoryOperations
|
||||
v-if="showControls"
|
||||
v-if="shouldShowControls"
|
||||
:history="history"
|
||||
:show-selection="showSelection"
|
||||
:expanded-count="expandedCount"
|
||||
:has-matches="hasMatches(itemsLoaded)"
|
||||
:has-matches="hasMatches(historyItems)"
|
||||
:operation-running.sync="operationRunning"
|
||||
@update:show-selection="setShowSelection"
|
||||
@collapse-all="collapseAll">
|
||||
@@ -69,23 +431,27 @@
|
||||
@operation-error="onOperationError"
|
||||
@hide-selection="onHideSelection"
|
||||
@reset-selection="resetSelection" />
|
||||
|
||||
<HistorySelectionStatus
|
||||
v-if="showSelection"
|
||||
:selection-size="selectionSize"
|
||||
@select-all="selectAllInCurrentQuery(itemsLoaded)"
|
||||
@select-all="selectAllInCurrentQuery(historyItems)"
|
||||
@reset-selection="resetSelection" />
|
||||
</template>
|
||||
</HistoryOperations>
|
||||
|
||||
<SelectionChangeWarning :query-selection-break="querySelectionBreak" />
|
||||
|
||||
<OperationErrorDialog
|
||||
v-if="operationError"
|
||||
:operation-error="operationError"
|
||||
@hide="operationError = null" />
|
||||
</section>
|
||||
|
||||
<section v-if="!showAdvanced" class="position-relative flex-grow-1 scroller">
|
||||
<HistoryDropZone v-if="showDropZone" />
|
||||
<div>
|
||||
<div v-if="loading && itemsLoaded && itemsLoaded.length === 0">
|
||||
<div v-if="isLoading && historyItems && historyItems.length === 0">
|
||||
<b-alert class="m-2" variant="info" show>
|
||||
<LoadingSpan message="Loading History" />
|
||||
</b-alert>
|
||||
@@ -93,8 +459,9 @@
|
||||
<b-alert v-else-if="isProcessing" class="m-2" variant="info" show>
|
||||
<LoadingSpan message="Processing operation" />
|
||||
</b-alert>
|
||||
<div v-else-if="itemsLoaded.length === 0">
|
||||
<HistoryEmpty v-if="queryDefault" :writable="writable" class="m-2" />
|
||||
<div v-else-if="historyItems.length === 0">
|
||||
<HistoryEmpty v-if="queryDefault" :writable="canEditHistory" class="m-2" />
|
||||
|
||||
<b-alert v-else-if="formattedSearchError" class="m-2" variant="danger" show>
|
||||
Error in filter:
|
||||
<a href="javascript:void(0)" @click="showAdvanced = true">
|
||||
@@ -108,26 +475,26 @@
|
||||
<ListingLayout
|
||||
v-else
|
||||
:offset="listOffset"
|
||||
:items="itemsLoaded"
|
||||
:items="historyItems"
|
||||
:query-key="queryKey"
|
||||
@scroll="onScroll">
|
||||
<template v-slot:item="{ item, currentOffset }">
|
||||
<ContentItem
|
||||
v-if="!invisible[item.hid]"
|
||||
v-if="!invisibleHistoryItems[item.hid]"
|
||||
:id="item.hid"
|
||||
is-history-item
|
||||
:item="item"
|
||||
:name="item.name"
|
||||
:writable="writable"
|
||||
:writable="canEditHistory"
|
||||
:expand-dataset="isExpanded(item)"
|
||||
:is-dataset="isDataset(item)"
|
||||
:highlight="getHighlight(item)"
|
||||
:selected="isSelected(item)"
|
||||
:selectable="showSelection"
|
||||
:filterable="filterable"
|
||||
@tag-click="updateFilterVal('tag', $event)"
|
||||
@tag-click="updateFilterValue('tag', $event)"
|
||||
@tag-change="onTagChange"
|
||||
@toggleHighlights="updateFilterVal('related', item.hid)"
|
||||
@toggleHighlights="updateFilterValue('related', item.hid)"
|
||||
@update:expand-dataset="setExpanded(item, $event)"
|
||||
@update:selected="setSelected(item, $event)"
|
||||
@view-collection="$emit('view-collection', item, currentOffset)"
|
||||
@@ -142,307 +509,3 @@
|
||||
</SelectedItems>
|
||||
</ExpandedItems>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMenu from "components/Common/FilterMenu";
|
||||
import ContentItem from "components/History/Content/ContentItem";
|
||||
import ExpandedItems from "components/History/Content/ExpandedItems";
|
||||
import SelectedItems from "components/History/Content/SelectedItems";
|
||||
import { HistoryFilters } from "components/History/HistoryFilters";
|
||||
import ListingLayout from "components/History/Layout/ListingLayout";
|
||||
import { deleteContent, updateContentFields } from "components/History/model/queries";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import { Toast } from "composables/toast";
|
||||
import { mapActions, mapState, storeToRefs } from "pinia";
|
||||
import { rewatchHistory } from "store/historyStore/model/watchHistory";
|
||||
import { useHistoryItemsStore } from "stores/historyItemsStore";
|
||||
import { useHistoryStore } from "stores/historyStore";
|
||||
import { getOperatorForAlias } from "utils/filtering";
|
||||
import Vue from "vue";
|
||||
|
||||
import { copyDataset } from "@/api/datasets";
|
||||
|
||||
import HistoryCounter from "./HistoryCounter";
|
||||
import HistoryDetails from "./HistoryDetails";
|
||||
import HistoryDropZone from "./HistoryDropZone";
|
||||
import HistoryEmpty from "./HistoryEmpty";
|
||||
import HistoryMessages from "./HistoryMessages";
|
||||
import HistoryOperations from "./HistoryOperations/HistoryOperations";
|
||||
import OperationErrorDialog from "./HistoryOperations/OperationErrorDialog";
|
||||
import SelectionChangeWarning from "./HistoryOperations/SelectionChangeWarning";
|
||||
import HistorySelectionOperations from "./HistoryOperations/SelectionOperations";
|
||||
import HistorySelectionStatus from "./HistoryOperations/SelectionStatus";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentItem,
|
||||
ExpandedItems,
|
||||
FilterMenu,
|
||||
HistoryCounter,
|
||||
HistoryMessages,
|
||||
HistoryDetails,
|
||||
HistoryDropZone,
|
||||
HistoryEmpty,
|
||||
HistoryOperations,
|
||||
HistorySelectionOperations,
|
||||
HistorySelectionStatus,
|
||||
LoadingSpan,
|
||||
ListingLayout,
|
||||
SelectedItems,
|
||||
SelectionChangeWarning,
|
||||
OperationErrorDialog,
|
||||
},
|
||||
props: {
|
||||
listOffset: { type: Number, default: 0 },
|
||||
history: { type: Object, required: true },
|
||||
filter: { type: String, default: "" },
|
||||
writable: { type: Boolean, default: true },
|
||||
showControls: { type: Boolean, default: true },
|
||||
filterable: { type: Boolean, default: false },
|
||||
},
|
||||
setup() {
|
||||
const { currentFilterText, currentHistoryId } = storeToRefs(useHistoryStore());
|
||||
const { lastCheckedTime, totalMatchesCount, isWatching } = storeToRefs(useHistoryItemsStore());
|
||||
return { currentFilterText, currentHistoryId, lastCheckedTime, totalMatchesCount, isWatching };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filterText: "",
|
||||
filterClass: HistoryFilters,
|
||||
invisible: {},
|
||||
loading: false,
|
||||
offset: 0,
|
||||
searchError: null,
|
||||
showAdvanced: false,
|
||||
showDropZone: false,
|
||||
operationRunning: null,
|
||||
operationError: null,
|
||||
querySelectionBreak: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useHistoryItemsStore, ["getHistoryItems"]),
|
||||
/** @returns {String} */
|
||||
historyId() {
|
||||
return this.history.id;
|
||||
},
|
||||
/** @returns {Date} */
|
||||
historyUpdateTime() {
|
||||
return this.history.update_time;
|
||||
},
|
||||
/** @returns {String} */
|
||||
queryKey() {
|
||||
return `${this.historyId}-${this.filterText}`;
|
||||
},
|
||||
/** @returns {Boolean} */
|
||||
queryDefault() {
|
||||
return !this.filterText;
|
||||
},
|
||||
/** @returns {Boolean} */
|
||||
hasFilters() {
|
||||
return !this.queryDefault;
|
||||
},
|
||||
/** @returns {Boolean} */
|
||||
isProcessing() {
|
||||
return this.operationRunning >= this.history.update_time;
|
||||
},
|
||||
/** @returns {Array} */
|
||||
itemsLoaded() {
|
||||
return this.getHistoryItems(this.historyId, this.filterText);
|
||||
},
|
||||
/** @returns {Object} */
|
||||
formattedSearchError() {
|
||||
if (this.searchError) {
|
||||
const { column, col, operation, op, value, val, err_msg, ValueError } = this.searchError;
|
||||
const alias = operation || op;
|
||||
const operator = alias ? getOperatorForAlias(alias) : ":";
|
||||
const formatted = {
|
||||
filter: `${column || col}${operator}`,
|
||||
value: value || val,
|
||||
msg: err_msg,
|
||||
typeError: ValueError,
|
||||
};
|
||||
return formatted;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
/** @returns {String} */
|
||||
storeFilterText() {
|
||||
if (this.historyId === this.currentHistoryId) {
|
||||
return this.currentFilterText || "";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
queryKey() {
|
||||
this.invisible = {};
|
||||
this.offset = 0;
|
||||
this.loadHistoryItems();
|
||||
},
|
||||
historyId(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.operationRunning = null;
|
||||
}
|
||||
},
|
||||
filter(newVal) {
|
||||
this.filterText = newVal;
|
||||
},
|
||||
filterText(newVal) {
|
||||
if (this.filterable) {
|
||||
this.setFilterText(this.historyId, newVal);
|
||||
}
|
||||
},
|
||||
storeFilterText(newVal) {
|
||||
if (this.filterable) {
|
||||
this.filterText = newVal;
|
||||
}
|
||||
},
|
||||
offset() {
|
||||
this.loadHistoryItems();
|
||||
},
|
||||
async historyUpdateTime() {
|
||||
await this.loadHistoryItems();
|
||||
},
|
||||
itemsLoaded(newItems) {
|
||||
if (this.invisible) {
|
||||
newItems.forEach((item) => {
|
||||
if (this.invisible[item.hid]) {
|
||||
Vue.set(this.invisible, item.hid, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
// `filterable` here indicates if this is the current history panel
|
||||
if (this.filterable && !this.filter) {
|
||||
this.filterText = this.storeFilterText;
|
||||
}
|
||||
await this.loadHistoryItems();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useHistoryStore, ["loadHistoryById", "setFilterText", "updateHistory"]),
|
||||
...mapActions(useHistoryItemsStore, ["fetchHistoryItems"]),
|
||||
getHighlight(item) {
|
||||
const highlightsKey = this.filterClass.getFilterValue(this.filterText, "related");
|
||||
if (!this.loading) {
|
||||
if (highlightsKey == item.hid) {
|
||||
return "active";
|
||||
} else if (highlightsKey) {
|
||||
if (item.hid > highlightsKey) {
|
||||
return "output";
|
||||
} else {
|
||||
return "input";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
hasMatches(items) {
|
||||
return !!items && items.length > 0;
|
||||
},
|
||||
isDataset(item) {
|
||||
return item.history_content_type == "dataset";
|
||||
},
|
||||
async loadHistoryItems() {
|
||||
this.loading = true;
|
||||
try {
|
||||
await this.fetchHistoryItems(this.historyId, this.filterText, this.offset);
|
||||
this.searchError = null;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data && error.response.data.err_msg) {
|
||||
console.debug("HistoryPanel - Load items error:", error.response.data.err_msg);
|
||||
this.searchError = error.response.data;
|
||||
} else {
|
||||
console.debug("HistoryPanel - Load items error.", error);
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async onDelete(item, recursive = false) {
|
||||
this.loading = true;
|
||||
this.setInvisible(item);
|
||||
await deleteContent(item, { recursive: recursive });
|
||||
this.loading = false;
|
||||
},
|
||||
onHideSelection(selectedItems) {
|
||||
selectedItems.forEach((item) => {
|
||||
this.setInvisible(item);
|
||||
});
|
||||
},
|
||||
onScroll(offset) {
|
||||
this.offset = offset;
|
||||
},
|
||||
async onUndelete(item) {
|
||||
this.setInvisible(item);
|
||||
this.loading = true;
|
||||
await updateContentFields(item, { deleted: false });
|
||||
this.loading = false;
|
||||
},
|
||||
async onUnhide(item) {
|
||||
this.setInvisible(item);
|
||||
this.loading = true;
|
||||
await updateContentFields(item, { visible: true });
|
||||
this.loading = false;
|
||||
},
|
||||
reloadContents() {
|
||||
rewatchHistory();
|
||||
},
|
||||
setInvisible(item) {
|
||||
Vue.set(this.invisible, item.hid, true);
|
||||
},
|
||||
onTagChange(item, newTags) {
|
||||
item.tags = newTags;
|
||||
},
|
||||
onOperationError(error) {
|
||||
console.debug("HistoryPanel - Operation error.", error);
|
||||
this.operationError = error;
|
||||
},
|
||||
onDragEnter(e) {
|
||||
this.dragTarget = e.target;
|
||||
this.showDropZone = true;
|
||||
},
|
||||
onDragLeave(e) {
|
||||
if (this.dragTarget == e.target) {
|
||||
this.showDropZone = false;
|
||||
}
|
||||
},
|
||||
async onDrop(evt) {
|
||||
this.showDropZone = false;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(evt.dataTransfer.getData("text"))[0];
|
||||
} catch (error) {
|
||||
// this was not a valid object for this dropzone, ignore
|
||||
}
|
||||
if (data) {
|
||||
const dataSource = data.history_content_type === "dataset" ? "hda" : "hdca";
|
||||
if (data.history_id != this.historyId) {
|
||||
try {
|
||||
await copyDataset(data.id, this.historyId, data.history_content_type, dataSource);
|
||||
if (data.history_content_type === "dataset") {
|
||||
Toast.info("Dataset copied to history");
|
||||
} else {
|
||||
Toast.info("Collection copied to history");
|
||||
}
|
||||
this.loadHistoryById(this.historyId);
|
||||
} catch (error) {
|
||||
this.onError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
Toast.error(`${error}`);
|
||||
},
|
||||
updateFilterVal(newFilter, newVal) {
|
||||
this.filterText = this.filterClass.setFilterValue(this.filterText, newFilter, newVal);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getHistoryByIdFromServer, setCurrentHistoryOnServer } from "stores/serv
|
||||
import { useUserStore } from "stores/userStore";
|
||||
import { getLocalVue } from "tests/jest/helpers";
|
||||
|
||||
import ContentItem from "./Content/ContentItem";
|
||||
import HistoryView from "./HistoryView";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
@@ -65,10 +66,6 @@ async function createWrapper(localVue, currentUserId, history) {
|
||||
const wrapper = mount(HistoryView, {
|
||||
propsData: { id: history.id },
|
||||
localVue,
|
||||
stubs: {
|
||||
icon: { template: "<div></div>" },
|
||||
ContentItem: true,
|
||||
},
|
||||
provide: {
|
||||
store: {
|
||||
dispatch: jest.fn,
|
||||
@@ -121,12 +118,12 @@ describe("History center panel View", () => {
|
||||
expectCorrectLayout(wrapper);
|
||||
|
||||
// make sure all history items show up
|
||||
const historyItems = wrapper.findAll("contentitem-stub");
|
||||
const historyItems = wrapper.findAllComponents(ContentItem);
|
||||
expect(historyItems.length).toBe(10);
|
||||
for (let i = 0; i < historyItems.length; i++) {
|
||||
const hid = historyItems.length - i;
|
||||
expect(historyItems.at(i).attributes("id")).toBe(`${hid}`);
|
||||
expect(historyItems.at(i).attributes("name")).toBe(`Dataset ${hid}`);
|
||||
expect(historyItems.at(i).props("id")).toBe(hid);
|
||||
expect(historyItems.at(i).props("name")).toBe(`Dataset ${hid}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<b-alert v-if="showHistoryStateInfo" variant="info" show data-description="history state info">
|
||||
{{ historyStateInfoMessage }}
|
||||
</b-alert>
|
||||
|
||||
<div class="flex-row flex-grow-0 pb-3">
|
||||
<b-button
|
||||
v-if="userOwnsHistory"
|
||||
@@ -14,6 +15,7 @@
|
||||
@click="setCurrentHistory(history.id)">
|
||||
Switch to this history
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-if="canImportHistory"
|
||||
v-b-modal:copy-history-modal
|
||||
@@ -24,6 +26,7 @@
|
||||
Import this history
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<CollectionPanel
|
||||
v-if="selectedCollections.length && selectedCollections[0].history_id == id"
|
||||
:history="history"
|
||||
@@ -33,10 +36,11 @@
|
||||
<HistoryPanel
|
||||
v-else
|
||||
:history="history"
|
||||
:writable="canEditHistory"
|
||||
:show-controls="false"
|
||||
:can-edit-history="canEditHistory"
|
||||
:should-show-controls="false"
|
||||
filterable
|
||||
@view-collection="onViewCollection" />
|
||||
|
||||
<CopyModal id="copy-history-modal" :history="history" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { mergeArray } from "@/store/historyStore/model/utilities";
|
||||
import { LastQueue } from "@/utils/lastQueue";
|
||||
import { urlData } from "@/utils/url";
|
||||
|
||||
type HistoryItem = DatasetSummary | HDCASummary;
|
||||
export type HistoryItem = DatasetSummary | HDCASummary;
|
||||
|
||||
const limit = 100;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user