diff --git a/client/src/components/Common/TextSummary.vue b/client/src/components/Common/TextSummary.vue
index 277a46e07007..de9263f3687e 100644
--- a/client/src/components/Common/TextSummary.vue
+++ b/client/src/components/Common/TextSummary.vue
@@ -5,14 +5,32 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { computed } from "vue";
const props = defineProps({
+ /** The text to summarize */
description: {
type: String,
required: true,
},
+ /** If `true`, doesn't let unexpanded text go beyond height of one line */
+ oneLineSummary: {
+ type: Boolean,
+ default: false,
+ },
+ /** If `true`, doesn't show expand/collapse buttons */
+ noExpand: {
+ type: Boolean,
+ default: false,
+ },
+ /** The component to use for the summary, default = `
` */
+ component: {
+ type: String,
+ default: "p",
+ },
+ /** Used as the toggle for expanding summary */
showDetails: {
type: Boolean,
default: false,
},
+ /** The maximum length of the unexpanded text / summary */
maxLength: {
type: Number,
default: 150,
@@ -47,8 +65,11 @@ const text = computed(() =>
- {{ text }}
-
+ {{
+ props.description
+ }}
+ {{ text }}
+
+
+
diff --git a/client/src/components/History/CurrentHistory/HistoryDetails.vue b/client/src/components/History/CurrentHistory/HistoryDetails.vue
index 075100cc7517..fc38695c49e8 100644
--- a/client/src/components/History/CurrentHistory/HistoryDetails.vue
+++ b/client/src/components/History/CurrentHistory/HistoryDetails.vue
@@ -4,10 +4,26 @@
:annotation="history.annotation"
:tags="history.tags"
:writeable="writeable"
+ :summarized="summarized"
+ :update-time="history.update_time"
@save="onSave">
-
+
+
+
+
+
+ last edited
+
+
@@ -18,11 +34,15 @@ import { mapActions } from "pinia";
import short from "@/components/plugins/short.js";
import { useHistoryStore } from "@/stores/historyStore";
+import TextSummary from "@/components/Common/TextSummary.vue";
import DetailsLayout from "@/components/History/Layout/DetailsLayout.vue";
+import UtcDate from "@/components/UtcDate.vue";
export default {
components: {
DetailsLayout,
+ TextSummary,
+ UtcDate,
},
directives: {
short,
@@ -30,6 +50,7 @@ export default {
props: {
history: { type: Object, required: true },
writeable: { type: Boolean, default: true },
+ summarized: { type: Boolean, default: false },
},
methods: {
...mapActions(useHistoryStore, ["updateHistory"]),
diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue
index e335cfd8c709..496465b0fee2 100644
--- a/client/src/components/History/CurrentHistory/HistoryPanel.vue
+++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue
@@ -38,7 +38,11 @@
:search-error="searchError"
:show-advanced.sync="showAdvanced" />
-
+
-
+
@@ -72,11 +94,13 @@ import { mapState } from "pinia";
import short from "@/components/plugins/short.js";
import { useUserStore } from "@/stores/userStore";
+import TextSummary from "@/components/Common/TextSummary.vue";
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
export default {
components: {
StatelessTags,
+ TextSummary,
},
directives: {
short,
@@ -85,6 +109,7 @@ export default {
name: { type: String, default: null },
annotation: { type: String, default: null },
showAnnotation: { type: Boolean, default: true },
+ summarized: { type: Boolean, default: false },
tags: { type: Array, default: null },
writeable: { type: Boolean, default: true },
},
@@ -140,3 +165,14 @@ export default {
},
};
+
+
diff --git a/client/src/components/History/Modals/SelectorModal.vue b/client/src/components/History/Modals/SelectorModal.vue
index 00052ea4e50a..98311f6906dc 100644
--- a/client/src/components/History/Modals/SelectorModal.vue
+++ b/client/src/components/History/Modals/SelectorModal.vue
@@ -5,6 +5,7 @@ import { faArrowDown, faColumns, faSignInAlt } from "@fortawesome/free-solid-svg
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useInfiniteScroll } from "@vueuse/core";
import { BBadge, BButton, BButtonGroup, BFormGroup, BListGroup, BListGroupItem, BModal } from "bootstrap-vue";
+import { orderBy } from "lodash";
import isEqual from "lodash.isequal";
import { storeToRefs } from "pinia";
import { computed, onMounted, onUnmounted, type PropType, type Ref, ref, watch } from "vue";
@@ -72,10 +73,9 @@ const modal: Ref = ref(null);
const scrollableDiv: Ref = ref(null);
const historyStore = useHistoryStore();
-const { currentHistoryId, totalHistoryCount } = storeToRefs(useHistoryStore());
+const { currentHistoryId, totalHistoryCount, pinnedHistories } = storeToRefs(useHistoryStore());
const { currentUser } = storeToRefs(useUserStore());
-const pinnedHistories: Ref<{ id: string }[]> = computed(() => historyStore.pinnedHistories);
const hasNoResults = computed(() => filter.value && filtered.value.length == 0);
const validFilter = computed(() => filter.value && filter.value.length > 2);
const allLoaded = computed(() => totalHistoryCount.value <= filtered.value.length);
@@ -153,6 +153,12 @@ const filtered: Ref = computed(() => {
});
});
+/** if pinned histories and selected histories are equal */
+const pinnedSelectedEqual = computed(() => {
+ // uses `orderBy` to ensure same ids are found in both `{ id: string }[]` arrays
+ return isEqual(orderBy(pinnedHistories.value, ["id"], ["asc"]), orderBy(selectedHistories.value, ["id"], ["asc"]));
+});
+
function historyClicked(history: HistorySummary) {
if (props.multiple) {
const index = selectedHistories.value.findIndex((item) => item.id == history.id);
@@ -249,10 +255,18 @@ async function loadMore(noScroll = false) {
:active="selectedHistories.some((h) => h.id === history.id)"
@click="() => historyClicked(history)">
-
- {{ history.name }}
- (Current)
-
+
+
+ {{ history.name }}
+ (Current)
+
+
+ (currently pinned)
+
+
@@ -333,10 +347,11 @@ async function loadMore(noScroll = false) {
+
{{ selectedHistories.length }} histories selected
Change Selected
diff --git a/client/src/components/History/Multiple/MultipleView.test.js b/client/src/components/History/Multiple/MultipleView.test.js
index 13fa2ff4948b..6175c7c69bd1 100644
--- a/client/src/components/History/Multiple/MultipleView.test.js
+++ b/client/src/components/History/Multiple/MultipleView.test.js
@@ -10,55 +10,76 @@ import { getLocalVue } from "tests/jest/helpers";
import MultipleView from "./MultipleView";
-const COUNT = 8;
const USER_ID = "test-user-id";
-const CURRENT_HISTORY_ID = "test-history-id-0";
-
-const pinia = createPinia();
+const FIRST_HISTORY_ID = "test-history-id-0";
const getFakeHistorySummaries = (num, selectedIndex) => {
return Array.from({ length: num }, (_, index) => ({
- id: selectedIndex === index ? CURRENT_HISTORY_ID : `test-history-id-${index}`,
+ id: `test-history-id-${index}`,
name: `History-${index}`,
tags: [],
update_time: new Date().toISOString(),
}));
};
const currentUser = { id: USER_ID };
-const UserHistoriesMock = MockUserHistories({ id: CURRENT_HISTORY_ID }, getFakeHistorySummaries(COUNT, 0), false);
-
-const localVue = getLocalVue();
describe("MultipleView", () => {
- let wrapper;
- let axiosMock;
-
- beforeEach(async () => {
- axiosMock = new MockAdapter(axios);
- wrapper = mount(MultipleView, {
- pinia,
+ async function setUpWrapper(UserHistoriesMock, count, currentHistoryId) {
+ const axiosMock = new MockAdapter(axios);
+ axiosMock.onGet(`api/histories/${FIRST_HISTORY_ID}`).reply(200, {});
+ const wrapper = mount(MultipleView, {
+ pinia: createPinia(),
stubs: {
UserHistories: UserHistoriesMock,
HistoryPanel: true,
+ icon: { template: "" },
},
- localVue,
+ localVue: getLocalVue(),
});
const userStore = useUserStore();
userStore.currentUser = currentUser;
const historyStore = useHistoryStore();
- historyStore.setHistories(getFakeHistorySummaries(COUNT, 0));
- historyStore.setCurrentHistoryId(CURRENT_HISTORY_ID);
+ historyStore.setHistories(getFakeHistorySummaries(count, 0));
+ historyStore.setCurrentHistoryId(currentHistoryId);
await flushPromises();
- });
- afterEach(() => {
+ return { wrapper, axiosMock };
+ }
+
+ it("more than 4 histories should not show the current history", async () => {
+ const count = 8;
+ const currentHistoryId = FIRST_HISTORY_ID;
+
+ // Set up UserHistories and wrapper
+ const UserHistoriesMock = MockUserHistories({ id: currentHistoryId }, getFakeHistorySummaries(count, 0), false);
+ const { wrapper, axiosMock } = await setUpWrapper(UserHistoriesMock, count, currentHistoryId);
+
+ // Test: current (first) history should not be shown because only 4 latest are shown by default
+ expect(wrapper.find("button[title='Current History']").exists()).toBeFalsy();
+
+ expect(wrapper.find("button[title='Switch to this history']").exists()).toBeTruthy();
+
+ expect(wrapper.find("div[title='Currently showing 4 most recently updated histories']").exists()).toBeTruthy();
+
+ expect(wrapper.find("[data-description='open select histories modal']").exists()).toBeTruthy();
+
axiosMock.reset();
});
- it("should show the current history", async () => {
+ it("less than or equal to 4 histories should not show the current history", async () => {
+ const count = 3;
+ const currentHistoryId = FIRST_HISTORY_ID;
+
+ // Set up UserHistories and wrapper
+ const UserHistoriesMock = MockUserHistories({ id: currentHistoryId }, getFakeHistorySummaries(count, 0), false);
+ const { wrapper, axiosMock } = await setUpWrapper(UserHistoriesMock, count, currentHistoryId);
+
+ // Test: current (first) history should be shown because only 4 latest are shown by default, and count = 3
expect(wrapper.find("button[title='Current History']").exists()).toBeTruthy();
+
+ axiosMock.reset();
});
});
diff --git a/client/src/components/History/Multiple/MultipleView.vue b/client/src/components/History/Multiple/MultipleView.vue
index 048efe383dc9..9b2c5c92208f 100644
--- a/client/src/components/History/Multiple/MultipleView.vue
+++ b/client/src/components/History/Multiple/MultipleView.vue
@@ -1,10 +1,11 @@