Skip to content

Commit

Permalink
Merge pull request #18689 from ahmedhamidawan/compressed_hids_not_vis…
Browse files Browse the repository at this point in the history
…ible_history

[24.1] Show items with same `hid` but different `id` in history
  • Loading branch information
mvdbeek authored Aug 28, 2024
2 parents c7097a0 + 2d245ec commit 12ce6af
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 34 deletions.
95 changes: 66 additions & 29 deletions client/src/components/History/Content/ContentItem.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faCheckSquare, faSquare } from "@fortawesome/free-regular-svg-icons";
import { faArrowCircleDown, faArrowCircleUp, faCheckCircle, faSpinner } from "@fortawesome/free-solid-svg-icons";
import {
faArrowCircleDown,
faArrowCircleUp,
faCheckCircle,
faExchangeAlt,
faSpinner,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BBadge, BButton, BCollapse } from "bootstrap-vue";
import { computed, ref } from "vue";
Expand All @@ -21,7 +27,7 @@ import ContentOptions from "./ContentOptions.vue";
import DatasetDetails from "./Dataset/DatasetDetails.vue";
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
library.add(faArrowCircleUp, faArrowCircleDown, faCheckCircle, faSpinner);
library.add(faArrowCircleUp, faArrowCircleDown, faCheckCircle, faExchangeAlt, faSpinner);
const router = useRouter();
const route = useRoute();
Expand All @@ -41,6 +47,7 @@ interface Props {
selectable?: boolean;
filterable?: boolean;
isPlaceholder?: boolean;
isSubItem?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
Expand All @@ -55,6 +62,7 @@ const props = withDefaults(defineProps<Props>(), {
selectable: false,
filterable: false,
isPlaceholder: false,
isSubItem: false,
});
const emit = defineEmits<{
Expand All @@ -80,6 +88,7 @@ const entryPointStore = useEntryPointStore();
const eventStore = useEventStore();
const contentItem = ref<HTMLElement | null>(null);
const subItemsVisible = ref(false);
const jobState = computed(() => {
return new JobStateSummary(props.item);
Expand All @@ -101,6 +110,15 @@ const contentCls = computed(() => {
return `alert-${status}`;
}
});
const computedClass = computed(() => {
return {
"content-item m-1 p-0 rounded btn-transparent-background": true,
[contentCls.value]: true,
"being-used": Object.values(itemUrls.value).includes(route.path),
"range-select-anchor": props.isRangeSelectAnchor,
"sub-item": props.isSubItem,
};
});
const contentState = computed(() => {
return STATES[state.value] && STATES[state.value];
});
Expand Down Expand Up @@ -181,14 +199,6 @@ const itemUrls = computed<ItemUrls>(() => {
};
});
const isBeingUsed = computed(() => {
return Object.values(itemUrls.value).includes(route.path) ? "being-used" : "";
});
const rangeSelectClass = computed(() => {
return props.isRangeSelectAnchor ? "range-select-anchor" : "";
});
/** Based on the user's keyboard platform, checks if it is the
* typical key for selection (ctrl for windows/linux, cmd for mac)
*/
Expand All @@ -197,7 +207,8 @@ function isSelectKey(event: KeyboardEvent) {
}
function onKeyDown(event: KeyboardEvent) {
if (!(event.target as HTMLElement)?.classList?.contains("content-item")) {
const classList = (event.target as HTMLElement)?.classList;
if (!classList.contains("content-item") || classList.contains("sub-item")) {
return;
}
Expand Down Expand Up @@ -339,12 +350,12 @@ function unexpandedClick(event: Event) {
<div
:id="contentId"
ref="contentItem"
:class="['content-item m-1 p-0 rounded btn-transparent-background', contentCls, isBeingUsed, rangeSelectClass]"
:class="computedClass"
:data-hid="id"
:data-state="dataState"
tabindex="0"
role="button"
:draggable="props.item.accessible === false ? false : true"
:draggable="props.item.accessible === false || props.isSubItem || subItemsVisible ? false : true"
@dragstart="onDragStart"
@dragend="onDragEnd"
@keydown="onKeyDown">
Expand Down Expand Up @@ -394,26 +405,39 @@ function unexpandedClick(event: Event) {
<span class="id hid">{{ id }}:</span>
<span class="content-title name font-weight-bold">{{ name }}</span>
</span>
<span v-if="item.purged" class="align-self-start btn-group p-1">
<span v-if="item.purged" class="ml-auto align-self-start btn-group p-1">
<BBadge variant="secondary" title="This dataset has been permanently deleted">
<icon icon="burn" /> Purged
</BBadge>
</span>
<ContentOptions
v-if="!isPlaceholder && !item.purged"
:writable="writable"
:is-dataset="isDataset"
:is-deleted="item.deleted"
:is-history-item="isHistoryItem"
:is-visible="item.visible"
:state="state"
:item-urls="itemUrls"
@delete="onDelete"
@display="onDisplay"
@showCollectionInfo="onShowCollectionInfo"
@edit="onEdit"
@undelete="onUndelete"
@unhide="emit('unhide')" />
<span class="align-self-start btn-group">
<BButton
v-if="item.sub_items?.length && !isSubItem"
title="Show converted items"
tabindex="0"
class="display-btn px-1 align-items-center"
size="sm"
variant="link"
@click.prevent.stop="subItemsVisible = !subItemsVisible">
<FontAwesomeIcon :icon="faExchangeAlt" />
<span class="indicator">{{ item.sub_items?.length }}</span>
</BButton>
<ContentOptions
v-if="!isPlaceholder && !item.purged"
:writable="writable"
:is-dataset="isDataset"
:is-deleted="item.deleted"
:is-history-item="isHistoryItem"
:is-visible="item.visible"
:state="state"
:item-urls="itemUrls"
@delete="onDelete"
@display="onDisplay"
@showCollectionInfo="onShowCollectionInfo"
@edit="onEdit"
@undelete="onUndelete"
@unhide="emit('unhide')" />
</span>
</div>
</div>
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/no-static-element-interactions -->
Expand Down Expand Up @@ -448,6 +472,7 @@ function unexpandedClick(event: Event) {
@edit="onEdit"
@toggleHighlights="toggleHighlights" />
</BCollapse>
<slot name="sub_items" :sub-items-visible="subItemsVisible" />
</div>
</template>

Expand All @@ -462,6 +487,18 @@ function unexpandedClick(event: Event) {
word-break: break-all;
}
.indicator {
align-items: center;
border-radius: 50%;
color: $brand-primary;
display: flex;
justify-content: center;
height: 1.2rem;
position: absolute;
top: -0.3rem;
width: 1.2rem;
}
// improve focus visibility
&:deep(.btn:focus) {
box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
Expand Down
25 changes: 24 additions & 1 deletion client/src/components/History/CurrentHistory/HistoryPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,22 @@ function setItemDragstart(
@view-collection="$emit('view-collection', item, currentOffset)"
@delete="onDelete"
@undelete="onUndelete(item)"
@unhide="onUnhide(item)" />
@unhide="onUnhide(item)">
<template v-slot:sub_items="slotProps">
<div v-if="slotProps.subItemsVisible" class="pl-2 sub-items-content">
<ContentItem
v-for="subItem in item.sub_items"
:id="subItem.hid"
:key="subItem.id"
:item="subItem"
:name="subItem.name"
:expand-dataset="isExpanded(subItem)"
:is-dataset="isDataset(subItem)"
:is-sub-item="true"
@update:expand-dataset="setExpanded(subItem, $event)" />
</div>
</template>
</ContentItem>
</template>
</ListingLayout>
</div>
Expand All @@ -659,3 +674,11 @@ function setItemDragstart(
</SelectedItems>
</ExpandedItems>
</template>

<style scoped lang="scss">
@import "theme/blue.scss";
.sub-items-content {
background: $body-bg;
}
</style>
32 changes: 31 additions & 1 deletion client/src/store/historyStore/model/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,41 @@ export function mergeArray(id, payload, items, itemKey) {
const localItem = itemArray[itemIndex];
if (localItem.id == item.id) {
Object.keys(localItem).forEach((key) => {
localItem[key] = item[key];
// Maybe it's ok to overwrite the `sub_items` key here because that
// array might contain items not returned for current filter?
if (key !== "sub_items") {
localItem[key] = item[key];
}
});
}
// the `item.id` is different with the same `hid`,
// so we add a key `sub_items` to this item and add any other related item(s) to it
else {
// But first, we check a few conditions and based on those, possibly replace the `localItem` with the new item
if (
// 1: `localItem` is not in the `payload` (for filter)
new Date(item.create_time) < new Date(localItem.create_time) ||
// 2: `localItem` has fewer keys than the new item
Object.keys(localItem).length < Object.keys(item).length
) {
set(itemArray, itemIndex, item);
pushSubItem(item, localItem);
localItem.sub_items = [];
} else {
pushSubItem(localItem, item);
}
}
} else {
set(itemArray, itemIndex, item);
}
}
}

function pushSubItem(item, subItem) {
if (!item.sub_items) {
item["sub_items"] = [];
}
if (!item["sub_items"].find((i) => i.id === subItem.id)) {
item["sub_items"].push(subItem);
}
}
14 changes: 11 additions & 3 deletions client/src/stores/historyItemsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import { mergeArray } from "@/store/historyStore/model/utilities";
import { ActionSkippedError, LastQueue } from "@/utils/lastQueue";
import { urlData } from "@/utils/url";

type ExtendedHistoryItem = HistoryItemSummary & { sub_items?: HistoryItemSummary[] };

const limit = 100;

type ExpectedReturn = { stats: { total_matches: number }; contents: HistoryItemSummary[] };
const queue = new LastQueue<typeof urlData>(1000, true);

export const useHistoryItemsStore = defineStore("historyItemsStore", () => {
const items = ref<Record<string, HistoryItemSummary[]>>({});
const items = ref<Record<string, ExtendedHistoryItem[]>>({});
const itemKey = ref("hid");
const totalMatchesCount = ref<number | undefined>(undefined);
const lastCheckedTime = ref(new Date());
Expand All @@ -35,12 +37,18 @@ export const useHistoryItemsStore = defineStore("historyItemsStore", () => {
(filter: [string, string]) => !filter[0].includes("related")
);
const relatedHid = HistoryFilters.getFilterValue(filterText, "related");
const filtered = itemArray.filter((item: HistoryItemSummary) => {
const filtered = itemArray.filter((item: ExtendedHistoryItem) => {
if (!item) {
return false;
}
if (!HistoryFilters.testFilters(filters, item)) {
return false;
// filters don't pass on the item, but they might pass on any of its sub_items
if (
!item.sub_items ||
!item.sub_items.some((subItem) => HistoryFilters.testFilters(filters, subItem))
) {
return false;
}
}
const relationKey = `${historyId}-${relatedHid}-${item.hid}`;
if (relatedHid && !relatedItems.value[relationKey]) {
Expand Down

0 comments on commit 12ce6af

Please sign in to comment.